Skip to content

Commit 9f69bb2

Browse files
committed
feat(youtube): add recommended podcasts kiosk and cap results
1 parent 6ac89c6 commit 9f69bb2

File tree

5 files changed

+111
-18
lines changed

5 files changed

+111
-18
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,16 @@ public KioskList getKioskList() throws ExtractionException {
172172
new YoutubeTrendingLinkHandlerFactory(),
173173
"Recommended Lives"
174174
);
175-
list.setDefaultKiosk("Recommended Lives");
175+
list.addKioskEntry(
176+
(streamingService, url, id) -> new YoutubeTrendingExtractor(
177+
YoutubeService.this,
178+
new YoutubeTrendingLinkHandlerFactory().fromUrl(url),
179+
id
180+
),
181+
new YoutubeTrendingLinkHandlerFactory(),
182+
"Recommended Podcasts"
183+
);
184+
list.setDefaultKiosk("Recommended Podcasts");
176185
} catch (final Exception e) {
177186
throw new ExtractionException(e);
178187
}

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

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@
4747
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
4848

4949
public class YoutubeTrendingExtractor extends KioskExtractor<StreamInfoItem> {
50+
private static final String KIOSK_RECOMMENDED_PODCASTS = "Recommended Podcasts";
51+
private static final String RECOMMENDED_LIVES_BROWSE_ID = "UC4R8DWoMoI7CAwX8_LjQHig";
52+
private static final String RECOMMENDED_PODCASTS_BROWSE_ID = "FEpodcasts_destination";
53+
private static final String RECOMMENDED_PODCASTS_PARAMS = "qgcCCAM=";
54+
private static final long RECOMMENDED_PODCASTS_MAX_ITEMS = 40;
55+
5056
private JsonObject initialData;
5157

5258
public YoutubeTrendingExtractor(final StreamingService service,
@@ -58,13 +64,21 @@ public YoutubeTrendingExtractor(final StreamingService service,
5864
@Override
5965
public void onFetchPage(@Nonnull final Downloader downloader)
6066
throws IOException, ExtractionException {
61-
// @formatter:off
62-
final byte[] body = JsonWriter.string(prepareDesktopJsonBuilder(getExtractorLocalization(),
63-
getExtractorContentCountry())
64-
.value("browseId", "UC4R8DWoMoI7CAwX8_LjQHig")
65-
.done())
66-
.getBytes(UTF_8);
67-
// @formatter:on
67+
final byte[] body;
68+
if (KIOSK_RECOMMENDED_PODCASTS.equals(getId())) {
69+
body = JsonWriter.string(prepareDesktopJsonBuilder(getExtractorLocalization(),
70+
getExtractorContentCountry())
71+
.value("browseId", RECOMMENDED_PODCASTS_BROWSE_ID)
72+
.value("params", RECOMMENDED_PODCASTS_PARAMS)
73+
.done())
74+
.getBytes(UTF_8);
75+
} else {
76+
body = JsonWriter.string(prepareDesktopJsonBuilder(getExtractorLocalization(),
77+
getExtractorContentCountry())
78+
.value("browseId", RECOMMENDED_LIVES_BROWSE_ID)
79+
.done())
80+
.getBytes(UTF_8);
81+
}
6882

6983
initialData = getJsonPostResponse("browse", body, getExtractorLocalization());
7084
}
@@ -86,6 +100,8 @@ public String getName() throws ParsingException {
86100
} else if (header.has("carouselHeaderRenderer")) {
87101
name = header.getObject("carouselHeaderRenderer").getArray("contents").getObject(0)
88102
.getObject("topicChannelDetailsRenderer").getObject("title").getString("simpleText");
103+
} else if (header.has("pageHeaderRenderer")) {
104+
name = header.getObject("pageHeaderRenderer").getString("pageTitle");
89105
}
90106

91107
if (isNullOrEmpty(name)) {
@@ -99,6 +115,9 @@ public String getName() throws ParsingException {
99115
public InfoItemsPage<StreamInfoItem> getInitialPage() throws ParsingException {
100116
final StreamInfoItemsCollector collector = new StreamInfoItemsCollector(getServiceId());
101117
final TimeAgoParser timeAgoParser = getTimeAgoParser();
118+
final long maximumItems = KIOSK_RECOMMENDED_PODCASTS.equals(getId())
119+
? RECOMMENDED_PODCASTS_MAX_ITEMS
120+
: Long.MAX_VALUE;
102121
final JsonObject tabContent = getTrendingTabRenderer().getObject("content");
103122

104123
if (tabContent.has("richGridRenderer")) {
@@ -110,8 +129,10 @@ public InfoItemsPage<StreamInfoItem> getInitialPage() throws ParsingException {
110129
// Filter Trending shorts and Recently trending sections
111130
.filter(content -> content.has("richItemRenderer"))
112131
.map(content -> content.getObject("richItemRenderer")
113-
.getObject("content")
114-
.getObject("videoRenderer"))
132+
.getObject("content"))
133+
.filter(content -> content.has("videoRenderer"))
134+
.map(content -> content.getObject("videoRenderer"))
135+
.limit(maximumItems)
115136
.forEachOrdered(videoRenderer -> collector.commit(
116137
new YoutubeStreamInfoItemExtractor(videoRenderer, timeAgoParser)));
117138
} else if (tabContent.has("sectionListRenderer")) {
@@ -136,6 +157,7 @@ public InfoItemsPage<StreamInfoItem> getInitialPage() throws ParsingException {
136157
.filter(JsonObject.class::isInstance)
137158
.map(JsonObject.class::cast)
138159
.map(item -> item.getObject("videoRenderer"))
160+
.limit(maximumItems)
139161
.forEachOrdered(videoRenderer -> collector.commit(
140162
new YoutubeStreamInfoItemExtractor(videoRenderer, timeAgoParser)));
141163
}
@@ -159,6 +181,7 @@ public InfoItemsPage<StreamInfoItem> getInitialPage() throws ParsingException {
159181
.filter(JsonObject.class::isInstance)
160182
.map(JsonObject.class::cast)
161183
.map(item -> item.getObject("gridVideoRenderer"))
184+
.limit(maximumItems)
162185
.forEachOrdered(videoRenderer -> collector.commit(
163186
new YoutubeStreamInfoItemExtractor(videoRenderer, timeAgoParser)));
164187
}
@@ -182,11 +205,12 @@ public InfoItemsPage<StreamInfoItem> getInitialPage() throws ParsingException {
182205
.map(content -> content.getObject("richItemRenderer")
183206
.getObject("content")
184207
.getObject("videoRenderer"))
208+
.limit(maximumItems)
185209
.forEachOrdered(videoRenderer -> collector.commit(
186210
new YoutubeStreamInfoItemExtractor(videoRenderer, timeAgoParser)));
187211
}
188212
if (collector.getItems().isEmpty()) {
189-
throw new ParsingException("Could not get trending page");
213+
throw new ParsingException("Could not get kiosk page: " + getId());
190214
}
191215

192216
if (ServiceList.YouTube.getFilterTypes().contains("recommendations")) {

extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/linkHandler/YoutubeTrendingLinkHandlerFactory.java

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,21 +34,40 @@
3434

3535
public class YoutubeTrendingLinkHandlerFactory extends ListLinkHandlerFactory {
3636

37+
private static final String TRENDING_ID = "Trending";
38+
private static final String RECOMMENDED_LIVES_ID = "Recommended Lives";
39+
private static final String RECOMMENDED_PODCASTS_ID = "Recommended Podcasts";
40+
private static final String TRENDING_URL = "https://www.youtube.com/feed/trending";
41+
private static final String RECOMMENDED_LIVES_URL =
42+
"https://www.youtube.com/channel/UC4R8DWoMoI7CAwX8_LjQHig";
43+
private static final String RECOMMENDED_PODCASTS_URL = "https://www.youtube.com/podcasts/videos";
44+
3745
public String getUrl(final String id,
3846
final List<FilterItem> contentFilters,
3947
final List<FilterItem> sortFilter) {
40-
if(!id.equals("Trending")){
41-
return "https://www.youtube.com/channel/UC4R8DWoMoI7CAwX8_LjQHig";
48+
if (TRENDING_ID.equals(id)) {
49+
return TRENDING_URL;
50+
}
51+
52+
if (RECOMMENDED_PODCASTS_ID.equals(id)) {
53+
return RECOMMENDED_PODCASTS_URL;
4254
}
43-
return "https://www.youtube.com/feed/trending";
55+
56+
return RECOMMENDED_LIVES_URL;
4457
}
4558

4659
@Override
4760
public String getId(final String url) {
48-
if(url.equals("https://www.youtube.com/feed/trending")){
49-
return "Trending";
61+
if (TRENDING_URL.equals(url)) {
62+
return TRENDING_ID;
5063
}
51-
return "Recommended Lives";
64+
65+
if (RECOMMENDED_PODCASTS_URL.equals(url)
66+
|| "https://www.youtube.com/podcasts".equals(url)) {
67+
return RECOMMENDED_PODCASTS_ID;
68+
}
69+
70+
return RECOMMENDED_LIVES_ID;
5271
}
5372

5473
@Override
@@ -62,6 +81,11 @@ public boolean onAcceptUrl(final String url) {
6281

6382
final String urlPath = urlObj.getPath();
6483
return Utils.isHTTP(urlObj) && (isYoutubeURL(urlObj) || isInvidiousURL(urlObj))
65-
&& (urlPath.equals("/feed/trending") || urlPath.equals("/channel/UC4R8DWoMoI7CAwX8_LjQHig"));
84+
&& (urlPath.equals("/feed/trending")
85+
|| urlPath.equals("/channel/UC4R8DWoMoI7CAwX8_LjQHig")
86+
|| urlPath.equals("/podcasts")
87+
|| urlPath.equals("/podcasts/")
88+
|| urlPath.equals("/podcasts/videos")
89+
|| urlPath.equals("/podcasts/videos/"));
6690
}
6791
}

extractor/src/test/java/org/schabi/newpipe/extractor/services/youtube/YoutubeServiceTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ void testGetKioskAvailableKiosks() {
5454
assertFalse(kioskList.getAvailableKiosks().isEmpty(), "No kiosk got returned");
5555
}
5656

57+
@Test
58+
void testRecommendedPodcastsKioskAvailable() {
59+
assertTrue(kioskList.getAvailableKiosks().contains("Recommended Podcasts"));
60+
}
61+
5762
@Test
5863
void testGetDefaultKiosk() throws Exception {
5964
assertEquals(kioskList.getDefaultKioskExtractor(null).getId(), "Trending");
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package org.schabi.newpipe.extractor.services.youtube;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.schabi.newpipe.extractor.services.youtube.linkHandler.YoutubeTrendingLinkHandlerFactory;
5+
6+
import static org.junit.jupiter.api.Assertions.assertEquals;
7+
import static org.junit.jupiter.api.Assertions.assertTrue;
8+
9+
public class YoutubeTrendingLinkHandlerFactoryPodcastsTest {
10+
11+
private static final YoutubeTrendingLinkHandlerFactory FACTORY =
12+
new YoutubeTrendingLinkHandlerFactory();
13+
14+
@Test
15+
public void testRecommendedPodcastsUrlFromId() throws Exception {
16+
assertEquals("https://www.youtube.com/podcasts/videos",
17+
FACTORY.fromId("Recommended Podcasts").getUrl());
18+
}
19+
20+
@Test
21+
public void testRecommendedPodcastsIdFromUrl() throws Exception {
22+
assertEquals("Recommended Podcasts",
23+
FACTORY.fromUrl("https://www.youtube.com/podcasts/videos").getId());
24+
}
25+
26+
@Test
27+
public void testAcceptPodcastsUrls() throws Exception {
28+
assertTrue(FACTORY.acceptUrl("https://www.youtube.com/podcasts/videos"));
29+
assertTrue(FACTORY.acceptUrl("https://www.youtube.com/podcasts"));
30+
}
31+
}

0 commit comments

Comments
 (0)