Skip to content

Commit 7936987

Browse files
authored
Merge pull request #1092 from Stypox/channel-tabs-improvements
Channel tabs code improvements
2 parents 95a3cc0 + 6d22271 commit 7936987

29 files changed

Lines changed: 2568 additions & 2569 deletions

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelHelper.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ public static String resolveChannelId(@Nonnull final String idOrPath)
4343
}
4444

4545
// If the URL is not a /channel URL, we need to use the navigation/resolve_url endpoint of
46-
// the InnerTube API to get the channel id.
47-
// Otherwise, we couldn't get information about the channel associated with this URL, if
48-
// there is one.
46+
// the InnerTube API to get the channel id. If this fails or if the URL is not a /channel
47+
// URL, then no information about the channel associated with this URL was found,
48+
// so the unresolved url will be returned.
4949
if (!channelId[0].equals("channel")) {
5050
final byte[] body = JsonWriter.string(
5151
prepareDesktopJsonBuilder(Localization.DEFAULT, ContentCountry.DEFAULT)
@@ -78,6 +78,7 @@ public static String resolveChannelId(@Nonnull final String idOrPath)
7878
}
7979
}
8080

81+
// return the unresolved URL
8182
return channelId[1];
8283
}
8384

@@ -110,11 +111,11 @@ private ChannelResponseData(@Nonnull final JsonObject jsonResponse,
110111
* Fetch a YouTube channel tab response, using the given channel ID and tab parameters.
111112
*
112113
* <p>
113-
* Redirections to other channels such as are supported to up to 3 redirects, which could
114-
* happen for instance for localized channels or auto-generated ones such as the {@code Movies
115-
* and Shows} (channel IDs {@code UCuJcl0Ju-gPDoksRjK1ya-w}, {@code UChBfWrfBXL9wS6tQtgjt_OQ}
116-
* and {@code UCok7UTQQEP1Rsctxiv3gwSQ} of this channel redirect to the
117-
* {@code UClgRkhTL3_hImCAmdLfDE4g} one).
114+
* Redirections to other channels are supported to up to 3 redirects, which could happen for
115+
* instance for localized channels or for auto-generated ones. For instance, there are three IDs
116+
* of the auto-generated "Movies and Shows" channel, i.e. {@code UCuJcl0Ju-gPDoksRjK1ya-w},
117+
* {@code UChBfWrfBXL9wS6tQtgjt_OQ} and {@code UCok7UTQQEP1Rsctxiv3gwSQ}, and they all redirect
118+
* to the {@code UClgRkhTL3_hImCAmdLfDE4g} one.
118119
* </p>
119120
*
120121
* @param channelId a valid YouTube channel ID
@@ -177,7 +178,7 @@ public static ChannelResponseData getChannelResponse(@Nonnull final String chann
177178
}
178179

179180
if (ajaxJson == null) {
180-
throw new ExtractionException("Got no channel response");
181+
throw new ExtractionException("Got no channel response after 3 redirects");
181182
}
182183

183184
defaultAlertsCheck(ajaxJson);

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelExtractor.java

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
public class YoutubeChannelExtractor extends ChannelExtractor {
5858

5959
private JsonObject jsonResponse;
60+
6061
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
6162
private Optional<YoutubeChannelHelper.ChannelHeader> channelHeader;
6263

@@ -89,6 +90,7 @@ public void onFetchPage(@Nonnull final Downloader downloader)
8990
"EgZ2aWRlb3PyBgQKAjoA", getExtractorLocalization(), getExtractorContentCountry());
9091

9192
jsonResponse = data.jsonResponse;
93+
channelHeader = YoutubeChannelHelper.getChannelHeader(jsonResponse);
9294
channelId = data.channelId;
9395
channelAgeGateRenderer = getChannelAgeGateRenderer();
9496
}
@@ -115,12 +117,8 @@ private JsonObject getChannelAgeGateRenderer() {
115117
}
116118

117119
@Nonnull
118-
private Optional<YoutubeChannelHelper.ChannelHeader> getChannelHeader() {
119-
//noinspection OptionalAssignedToNull
120-
if (channelHeader == null) {
121-
channelHeader = YoutubeChannelHelper.getChannelHeader(jsonResponse);
122-
}
123-
return channelHeader;
120+
private Optional<JsonObject> getChannelHeaderJson() {
121+
return channelHeader.map(it -> it.json);
124122
}
125123

126124
@Nonnull
@@ -136,9 +134,9 @@ public String getUrl() throws ParsingException {
136134
@Nonnull
137135
@Override
138136
public String getId() throws ParsingException {
139-
return getChannelHeader()
140-
.flatMap(header -> Optional.ofNullable(header.json.getString("channelId"))
141-
.or(() -> Optional.ofNullable(header.json.getObject("navigationEndpoint")
137+
return getChannelHeaderJson()
138+
.flatMap(header -> Optional.ofNullable(header.getString("channelId"))
139+
.or(() -> Optional.ofNullable(header.getObject("navigationEndpoint")
142140
.getObject("browseEndpoint")
143141
.getString("browseId"))
144142
))
@@ -160,8 +158,8 @@ public String getName() throws ParsingException {
160158
return metadataRendererTitle;
161159
}
162160

163-
return getChannelHeader().flatMap(header -> {
164-
final Object title = header.json.get("title");
161+
return getChannelHeaderJson().flatMap(header -> {
162+
final Object title = header.get("title");
165163
if (title instanceof String) {
166164
return Optional.of((String) title);
167165
} else if (title instanceof JsonObject) {
@@ -180,7 +178,7 @@ public String getAvatarUrl() throws ParsingException {
180178
if (channelAgeGateRenderer != null) {
181179
avatarJsonObjectContainer = channelAgeGateRenderer;
182180
} else {
183-
avatarJsonObjectContainer = getChannelHeader().map(header -> header.json)
181+
avatarJsonObjectContainer = getChannelHeaderJson()
184182
.orElseThrow(() -> new ParsingException("Could not get avatar URL"));
185183
}
186184

@@ -196,8 +194,8 @@ public String getBannerUrl() throws ParsingException {
196194
return "";
197195
}
198196

199-
return getChannelHeader().flatMap(header -> Optional.ofNullable(
200-
header.json.getObject("banner")
197+
return getChannelHeaderJson().flatMap(header -> Optional.ofNullable(
198+
header.getObject("banner")
201199
.getArray("thumbnails")
202200
.getObject(0)
203201
.getString("url")))
@@ -226,9 +224,9 @@ public long getSubscriberCount() throws ParsingException {
226224
return UNKNOWN_SUBSCRIBER_COUNT;
227225
}
228226

229-
final Optional<YoutubeChannelHelper.ChannelHeader> headerOpt = getChannelHeader();
227+
final Optional<JsonObject> headerOpt = getChannelHeaderJson();
230228
if (headerOpt.isPresent()) {
231-
final JsonObject header = headerOpt.get().json;
229+
final JsonObject header = headerOpt.get();
232230
JsonObject textObject = null;
233231

234232
if (header.has("subscriberCountText")) {
@@ -285,9 +283,8 @@ public boolean isVerified() throws ParsingException {
285283
return false;
286284
}
287285

288-
final Optional<YoutubeChannelHelper.ChannelHeader> headerOpt = getChannelHeader();
289-
if (headerOpt.isPresent()) {
290-
final YoutubeChannelHelper.ChannelHeader header = headerOpt.get();
286+
if (channelHeader.isPresent()) {
287+
final YoutubeChannelHelper.ChannelHeader header = channelHeader.get();
291288

292289
// The CarouselHeaderRenderer does not contain any verification badges.
293290
// Since it is only shown on YT-internal channels or on channels of large organizations

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelTabExtractor.java

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.nio.charset.StandardCharsets;
2424
import java.util.List;
2525
import java.util.Optional;
26-
import java.util.function.Consumer;
2726

2827
import static org.schabi.newpipe.extractor.services.youtube.YoutubeChannelHelper.getChannelResponse;
2928
import static org.schabi.newpipe.extractor.services.youtube.YoutubeChannelHelper.resolveChannelId;
@@ -299,20 +298,20 @@ private Optional<JsonObject> collectItem(@Nonnull final MultiInfoItemsCollector
299298
.getObject("content");
300299

301300
if (richItem.has("videoRenderer")) {
302-
getCommitVideoConsumer(collector, timeAgoParser, channelIds).accept(
301+
getCommitVideoConsumer(collector, timeAgoParser, channelIds,
303302
richItem.getObject("videoRenderer"));
304303
} else if (richItem.has("reelItemRenderer")) {
305-
getCommitReelItemConsumer(collector, timeAgoParser, channelIds).accept(
304+
getCommitReelItemConsumer(collector, timeAgoParser, channelIds,
306305
richItem.getObject("reelItemRenderer"));
307306
} else if (richItem.has("playlistRenderer")) {
308-
getCommitPlaylistConsumer(collector, channelIds).accept(
307+
getCommitPlaylistConsumer(collector, channelIds,
309308
item.getObject("playlistRenderer"));
310309
}
311310
} else if (item.has("gridVideoRenderer")) {
312-
getCommitVideoConsumer(collector, timeAgoParser, channelIds).accept(
311+
getCommitVideoConsumer(collector, timeAgoParser, channelIds,
313312
item.getObject("gridVideoRenderer"));
314313
} else if (item.has("gridPlaylistRenderer")) {
315-
getCommitPlaylistConsumer(collector, channelIds).accept(
314+
getCommitPlaylistConsumer(collector, channelIds,
316315
item.getObject("gridPlaylistRenderer"));
317316
} else if (item.has("gridChannelRenderer")) {
318317
collector.commit(new YoutubeChannelInfoItemExtractor(
@@ -336,13 +335,12 @@ private Optional<JsonObject> collectItem(@Nonnull final MultiInfoItemsCollector
336335
return Optional.empty();
337336
}
338337

339-
@Nonnull
340-
private Consumer<JsonObject> getCommitVideoConsumer(
341-
@Nonnull final MultiInfoItemsCollector collector,
342-
@Nonnull final TimeAgoParser timeAgoParser,
343-
@Nonnull final List<String> channelIds) {
344-
return videoRenderer -> collector.commit(
345-
new YoutubeStreamInfoItemExtractor(videoRenderer, timeAgoParser) {
338+
private void getCommitVideoConsumer(@Nonnull final MultiInfoItemsCollector collector,
339+
@Nonnull final TimeAgoParser timeAgoParser,
340+
@Nonnull final List<String> channelIds,
341+
@Nonnull final JsonObject jsonObject) {
342+
collector.commit(
343+
new YoutubeStreamInfoItemExtractor(jsonObject, timeAgoParser) {
346344
@Override
347345
public String getUploaderName() throws ParsingException {
348346
if (channelIds.size() >= 2) {
@@ -361,13 +359,12 @@ public String getUploaderUrl() throws ParsingException {
361359
});
362360
}
363361

364-
@Nonnull
365-
private Consumer<JsonObject> getCommitReelItemConsumer(
366-
@Nonnull final MultiInfoItemsCollector collector,
367-
@Nonnull final TimeAgoParser timeAgoParser,
368-
@Nonnull final List<String> channelIds) {
369-
return reelItemRenderer -> collector.commit(
370-
new YoutubeReelInfoItemExtractor(reelItemRenderer, timeAgoParser) {
362+
private void getCommitReelItemConsumer(@Nonnull final MultiInfoItemsCollector collector,
363+
@Nonnull final TimeAgoParser timeAgoParser,
364+
@Nonnull final List<String> channelIds,
365+
@Nonnull final JsonObject jsonObject) {
366+
collector.commit(
367+
new YoutubeReelInfoItemExtractor(jsonObject, timeAgoParser) {
371368
@Override
372369
public String getUploaderName() throws ParsingException {
373370
if (channelIds.size() >= 2) {
@@ -386,12 +383,11 @@ public String getUploaderUrl() throws ParsingException {
386383
});
387384
}
388385

389-
@Nonnull
390-
private Consumer<JsonObject> getCommitPlaylistConsumer(
391-
@Nonnull final MultiInfoItemsCollector collector,
392-
@Nonnull final List<String> channelIds) {
393-
return playlistRenderer -> collector.commit(
394-
new YoutubePlaylistInfoItemExtractor(playlistRenderer) {
386+
private void getCommitPlaylistConsumer(@Nonnull final MultiInfoItemsCollector collector,
387+
@Nonnull final List<String> channelIds,
388+
@Nonnull final JsonObject jsonObject) {
389+
collector.commit(
390+
new YoutubePlaylistInfoItemExtractor(jsonObject) {
395391
@Override
396392
public String getUploaderName() throws ParsingException {
397393
if (channelIds.size() >= 2) {

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/extractors/YoutubeChannelTabPlaylistExtractor.java

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.schabi.newpipe.extractor.services.youtube.extractors;
22

3-
import org.schabi.newpipe.extractor.InfoItem;
3+
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
4+
45
import org.schabi.newpipe.extractor.Page;
56
import org.schabi.newpipe.extractor.StreamingService;
67
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
@@ -12,14 +13,11 @@
1213
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
1314
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
1415
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubePlaylistLinkHandlerFactory;
15-
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
1616

17-
import javax.annotation.Nonnull;
1817
import java.io.IOException;
19-
import java.util.ArrayList;
2018
import java.util.List;
2119

22-
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
20+
import javax.annotation.Nonnull;
2321

2422
/**
2523
* A {@link ChannelTabExtractor} for YouTube system playlists using a
@@ -74,35 +72,19 @@ public void onFetchPage(@Nonnull final Downloader downloader)
7472

7573
@Nonnull
7674
@Override
77-
public InfoItemsPage<InfoItem> getInitialPage() throws IOException, ExtractionException {
75+
public InfoItemsPage getInitialPage() throws IOException, ExtractionException {
7876
if (!playlistExisting) {
7977
return InfoItemsPage.emptyPage();
8078
}
81-
82-
final InfoItemsPage<StreamInfoItem> playlistInitialPage =
83-
playlistExtractorInstance.getInitialPage();
84-
85-
// We can't provide the playlist page as it is due to a type conflict, we need to wrap the
86-
// page items and provide a new InfoItemsPage
87-
final List<InfoItem> infoItems = new ArrayList<>(playlistInitialPage.getItems());
88-
return new InfoItemsPage<>(infoItems, playlistInitialPage.getNextPage(),
89-
playlistInitialPage.getErrors());
79+
return playlistExtractorInstance.getInitialPage();
9080
}
9181

9282
@Override
93-
public InfoItemsPage<InfoItem> getPage(final Page page)
94-
throws IOException, ExtractionException {
83+
public InfoItemsPage getPage(final Page page) throws IOException, ExtractionException {
9584
if (!playlistExisting) {
9685
return InfoItemsPage.emptyPage();
9786
}
98-
99-
final InfoItemsPage<StreamInfoItem> playlistPage = playlistExtractorInstance.getPage(page);
100-
101-
// We can't provide the playlist page as it is due to a type conflict, we need to wrap the
102-
// page items and provide a new InfoItemsPage
103-
final List<InfoItem> infoItems = new ArrayList<>(playlistPage.getItems());
104-
return new InfoItemsPage<>(infoItems, playlistPage.getNextPage(),
105-
playlistPage.getErrors());
87+
return playlistExtractorInstance.getPage(page);
10688
}
10789

10890
/**

extractor/src/test/java/org/schabi/newpipe/extractor/ExtractorAsserts.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ public static void assertContains(
151151
"'" + shouldBeContained + "' should be contained inside '" + container + "'");
152152
}
153153

154-
public static void assertTabsContained(@Nonnull final List<ListLinkHandler> tabs,
155-
@Nonnull final String... expectedTabs) {
154+
public static void assertTabsContain(@Nonnull final List<ListLinkHandler> tabs,
155+
@Nonnull final String... expectedTabs) {
156156
final Set<String> tabSet = tabs.stream()
157157
.map(linkHandler -> linkHandler.getContentFilters().get(0))
158158
.collect(Collectors.toUnmodifiableSet());

extractor/src/test/java/org/schabi/newpipe/extractor/services/bandcamp/BandcampChannelExtractorTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import org.schabi.newpipe.extractor.services.BaseChannelExtractorTest;
1212

1313
import static org.junit.jupiter.api.Assertions.*;
14-
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContained;
14+
import static org.schabi.newpipe.extractor.ExtractorAsserts.assertTabsContain;
1515
import static org.schabi.newpipe.extractor.ServiceList.Bandcamp;
1616

1717
public class BandcampChannelExtractorTest implements BaseChannelExtractorTest {
@@ -84,7 +84,7 @@ public void testOriginalUrl() throws Exception {
8484
@Test
8585
@Override
8686
public void testTabs() throws Exception {
87-
assertTabsContained(extractor.getTabs(), ChannelTabs.ALBUMS);
87+
assertTabsContain(extractor.getTabs(), ChannelTabs.ALBUMS);
8888
}
8989

9090
@Test

0 commit comments

Comments
 (0)