Skip to content

Commit 6589e2c

Browse files
authored
Merge pull request #1148 from Stypox/mediaccc-channel-tab-handler
[MediaCCC] Allow obtaining channel tab link handler
2 parents ad71864 + aaf3231 commit 6589e2c

File tree

6 files changed

+167
-79
lines changed

6 files changed

+167
-79
lines changed

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCService.java

+9-10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandlerFactory;
2020
import org.schabi.newpipe.extractor.playlist.PlaylistExtractor;
2121
import org.schabi.newpipe.extractor.search.SearchExtractor;
22+
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCChannelTabExtractor;
2223
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCConferenceExtractor;
2324
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCConferenceKiosk;
2425
import org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCLiveStreamExtractor;
@@ -57,7 +58,9 @@ public ListLinkHandlerFactory getChannelLHFactory() {
5758

5859
@Override
5960
public ListLinkHandlerFactory getChannelTabLHFactory() {
60-
return null;
61+
// there is just one channel tab in MediaCCC, the one containing conferences, so there is
62+
// no need for a specific channel tab link handler, but we can just use the channel one
63+
return MediaCCCConferenceLinkHandlerFactory.getInstance();
6164
}
6265

6366
@Override
@@ -86,17 +89,13 @@ public ChannelExtractor getChannelExtractor(final ListLinkHandler linkHandler) {
8689
@Override
8790
public ChannelTabExtractor getChannelTabExtractor(final ListLinkHandler linkHandler) {
8891
if (linkHandler instanceof ReadyChannelTabListLinkHandler) {
92+
// conference data has already been fetched, let the ReadyChannelTabListLinkHandler
93+
// create a MediaCCCChannelTabExtractor with that data
8994
return ((ReadyChannelTabListLinkHandler) linkHandler).getChannelTabExtractor(this);
95+
} else {
96+
// conference data has not been fetched yet, so pass null instead
97+
return new MediaCCCChannelTabExtractor(this, linkHandler, null);
9098
}
91-
92-
/*
93-
Channel tab extractors are only supported in conferences and should only come from a
94-
ReadyChannelTabListLinkHandler instance with a ChannelTabExtractorBuilder instance of the
95-
conferences extractor
96-
97-
If that's not the case, return null in this case, so no channel tabs support
98-
*/
99-
return null;
10099
}
101100

102101
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
2+
3+
import com.grack.nanojson.JsonObject;
4+
5+
import org.schabi.newpipe.extractor.InfoItem;
6+
import org.schabi.newpipe.extractor.ListExtractor;
7+
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
8+
import org.schabi.newpipe.extractor.Page;
9+
import org.schabi.newpipe.extractor.StreamingService;
10+
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
11+
import org.schabi.newpipe.extractor.downloader.Downloader;
12+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
13+
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
14+
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
15+
16+
import java.io.IOException;
17+
import java.util.Objects;
18+
19+
import javax.annotation.Nonnull;
20+
import javax.annotation.Nullable;
21+
22+
/**
23+
* MediaCCC does not really have channel tabs, but rather a list of videos for each conference,
24+
* so this class just acts as a videos channel tab extractor.
25+
*/
26+
public class MediaCCCChannelTabExtractor extends ChannelTabExtractor {
27+
@Nullable
28+
private JsonObject conferenceData;
29+
30+
/**
31+
* @param conferenceData will be not-null if conference data has already been fetched by
32+
* {@link MediaCCCConferenceExtractor}. Otherwise, if this parameter is
33+
* {@code null}, conference data will be fetched anew.
34+
*/
35+
public MediaCCCChannelTabExtractor(final StreamingService service,
36+
final ListLinkHandler linkHandler,
37+
@Nullable final JsonObject conferenceData) {
38+
super(service, linkHandler);
39+
this.conferenceData = conferenceData;
40+
}
41+
42+
@Override
43+
public void onFetchPage(@Nonnull final Downloader downloader)
44+
throws ExtractionException, IOException {
45+
if (conferenceData == null) {
46+
// only fetch conference data if we don't have it already
47+
conferenceData = MediaCCCConferenceExtractor.fetchConferenceData(downloader, getId());
48+
}
49+
}
50+
51+
@Nonnull
52+
@Override
53+
public ListExtractor.InfoItemsPage<InfoItem> getInitialPage() {
54+
final MultiInfoItemsCollector collector =
55+
new MultiInfoItemsCollector(getServiceId());
56+
Objects.requireNonNull(conferenceData) // will surely be != null after onFetchPage
57+
.getArray("events")
58+
.stream()
59+
.filter(JsonObject.class::isInstance)
60+
.map(JsonObject.class::cast)
61+
.forEach(event -> collector.commit(new MediaCCCStreamInfoItemExtractor(event)));
62+
return new ListExtractor.InfoItemsPage<>(collector, null);
63+
}
64+
65+
@Override
66+
public ListExtractor.InfoItemsPage<InfoItem> getPage(final Page page) {
67+
return ListExtractor.InfoItemsPage.emptyPage();
68+
}
69+
}
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
package org.schabi.newpipe.extractor.services.media_ccc.extractors;
22

3+
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
4+
35
import com.grack.nanojson.JsonObject;
46
import com.grack.nanojson.JsonParser;
57
import com.grack.nanojson.JsonParserException;
68

7-
import org.schabi.newpipe.extractor.InfoItem;
8-
import org.schabi.newpipe.extractor.ListExtractor;
9-
import org.schabi.newpipe.extractor.MultiInfoItemsCollector;
109
import org.schabi.newpipe.extractor.Image;
11-
import org.schabi.newpipe.extractor.Page;
1210
import org.schabi.newpipe.extractor.StreamingService;
1311
import org.schabi.newpipe.extractor.channel.ChannelExtractor;
14-
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
1512
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
1613
import org.schabi.newpipe.extractor.downloader.Downloader;
1714
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
1815
import org.schabi.newpipe.extractor.exceptions.ParsingException;
1916
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
2017
import org.schabi.newpipe.extractor.linkhandler.ReadyChannelTabListLinkHandler;
21-
import org.schabi.newpipe.extractor.services.media_ccc.extractors.infoItems.MediaCCCStreamInfoItemExtractor;
2218
import org.schabi.newpipe.extractor.services.media_ccc.linkHandler.MediaCCCConferenceLinkHandlerFactory;
2319

2420
import java.io.IOException;
@@ -27,8 +23,6 @@
2723

2824
import javax.annotation.Nonnull;
2925

30-
import static org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCParsingHelper.getImageListFromLogoImageUrl;
31-
3226
public class MediaCCCConferenceExtractor extends ChannelExtractor {
3327
private JsonObject conferenceData;
3428

@@ -37,6 +31,19 @@ public MediaCCCConferenceExtractor(final StreamingService service,
3731
super(service, linkHandler);
3832
}
3933

34+
static JsonObject fetchConferenceData(@Nonnull final Downloader downloader,
35+
@Nonnull final String conferenceId)
36+
throws IOException, ExtractionException {
37+
final String conferenceUrl
38+
= MediaCCCConferenceLinkHandlerFactory.CONFERENCE_API_ENDPOINT + conferenceId;
39+
try {
40+
return JsonParser.object().from(downloader.get(conferenceUrl).responseBody());
41+
} catch (final JsonParserException jpe) {
42+
throw new ExtractionException("Could not parse json returned by URL: " + conferenceUrl);
43+
}
44+
}
45+
46+
4047
@Nonnull
4148
@Override
4249
public List<Image> getAvatars() {
@@ -88,76 +95,22 @@ public boolean isVerified() {
8895
@Nonnull
8996
@Override
9097
public List<ListLinkHandler> getTabs() throws ParsingException {
91-
return List.of(new ReadyChannelTabListLinkHandler(getUrl(), getId(),
92-
ChannelTabs.VIDEOS, new VideosTabExtractorBuilder(conferenceData)));
98+
// avoid keeping a reference to MediaCCCConferenceExtractor inside the lambda
99+
final JsonObject theConferenceData = conferenceData;
100+
return List.of(new ReadyChannelTabListLinkHandler(getUrl(), getId(), ChannelTabs.VIDEOS,
101+
(service, linkHandler) ->
102+
new MediaCCCChannelTabExtractor(service, linkHandler, theConferenceData)));
93103
}
94104

95105
@Override
96106
public void onFetchPage(@Nonnull final Downloader downloader)
97107
throws IOException, ExtractionException {
98-
final String conferenceUrl
99-
= MediaCCCConferenceLinkHandlerFactory.CONFERENCE_API_ENDPOINT + getId();
100-
try {
101-
conferenceData = JsonParser.object().from(downloader.get(conferenceUrl).responseBody());
102-
} catch (final JsonParserException jpe) {
103-
throw new ExtractionException("Could not parse json returned by URL: " + conferenceUrl);
104-
}
108+
conferenceData = fetchConferenceData(downloader, getId());
105109
}
106110

107111
@Nonnull
108112
@Override
109113
public String getName() throws ParsingException {
110114
return conferenceData.getString("title");
111115
}
112-
113-
private static final class VideosTabExtractorBuilder
114-
implements ReadyChannelTabListLinkHandler.ChannelTabExtractorBuilder {
115-
116-
private final JsonObject conferenceData;
117-
118-
VideosTabExtractorBuilder(final JsonObject conferenceData) {
119-
this.conferenceData = conferenceData;
120-
}
121-
122-
@Nonnull
123-
@Override
124-
public ChannelTabExtractor build(@Nonnull final StreamingService service,
125-
@Nonnull final ListLinkHandler linkHandler) {
126-
return new VideosChannelTabExtractor(service, linkHandler, conferenceData);
127-
}
128-
}
129-
130-
private static final class VideosChannelTabExtractor extends ChannelTabExtractor {
131-
private final JsonObject conferenceData;
132-
133-
VideosChannelTabExtractor(final StreamingService service,
134-
final ListLinkHandler linkHandler,
135-
final JsonObject conferenceData) {
136-
super(service, linkHandler);
137-
this.conferenceData = conferenceData;
138-
}
139-
140-
@Override
141-
public void onFetchPage(@Nonnull final Downloader downloader) {
142-
// Nothing to do here, as data was already fetched
143-
}
144-
145-
@Nonnull
146-
@Override
147-
public ListExtractor.InfoItemsPage<InfoItem> getInitialPage() {
148-
final MultiInfoItemsCollector collector =
149-
new MultiInfoItemsCollector(getServiceId());
150-
conferenceData.getArray("events")
151-
.stream()
152-
.filter(JsonObject.class::isInstance)
153-
.map(JsonObject.class::cast)
154-
.forEach(event -> collector.commit(new MediaCCCStreamInfoItemExtractor(event)));
155-
return new InfoItemsPage<>(collector, null);
156-
}
157-
158-
@Override
159-
public InfoItemsPage<InfoItem> getPage(final Page page) {
160-
return InfoItemsPage.emptyPage();
161-
}
162-
}
163116
}

extractor/src/main/java/org/schabi/newpipe/extractor/services/media_ccc/linkHandler/MediaCCCConferenceLinkHandlerFactory.java

+17
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
package org.schabi.newpipe.extractor.services.media_ccc.linkHandler;
22

3+
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
34
import org.schabi.newpipe.extractor.exceptions.ParsingException;
45
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandlerFactory;
56
import org.schabi.newpipe.extractor.utils.Parser;
67

78
import java.util.List;
89

10+
/**
11+
* Since MediaCCC does not really have channel tabs (i.e. it only has one single "tab" with videos),
12+
* this link handler acts both as the channel link handler and the channel tab link handler. That's
13+
* why {@link #getAvailableContentFilter()} has been overridden.
14+
*/
915
public final class MediaCCCConferenceLinkHandlerFactory extends ListLinkHandlerFactory {
1016

1117
private static final MediaCCCConferenceLinkHandlerFactory INSTANCE
@@ -46,4 +52,15 @@ public boolean onAcceptUrl(final String url) {
4652
return false;
4753
}
4854
}
55+
56+
/**
57+
* @see MediaCCCConferenceLinkHandlerFactory
58+
* @return MediaCCC's only channel "tab", i.e. {@link ChannelTabs#VIDEOS}
59+
*/
60+
@Override
61+
public String[] getAvailableContentFilter() {
62+
return new String[]{
63+
ChannelTabs.VIDEOS,
64+
};
65+
}
4966
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.schabi.newpipe.extractor.services.media_ccc;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
5+
6+
import org.junit.jupiter.api.BeforeAll;
7+
import org.junit.jupiter.api.Test;
8+
import org.schabi.newpipe.downloader.DownloaderTestImpl;
9+
import org.schabi.newpipe.extractor.NewPipe;
10+
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabExtractor;
11+
import org.schabi.newpipe.extractor.channel.tabs.ChannelTabs;
12+
13+
/**
14+
* Test that it is possible to create and use a channel tab extractor ({@link
15+
* org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCChannelTabExtractor}) without
16+
* passing through the conference extractor
17+
*/
18+
public class MediaCCCChannelTabExtractorTest {
19+
public static class CCCamp2023 {
20+
private static ChannelTabExtractor extractor;
21+
22+
@BeforeAll
23+
public static void setUpClass() throws Exception {
24+
NewPipe.init(DownloaderTestImpl.getInstance());
25+
extractor = MediaCCC.getChannelTabExtractorFromId("camp2023", ChannelTabs.VIDEOS);
26+
extractor.fetchPage();
27+
}
28+
29+
@Test
30+
void testName() {
31+
assertEquals(ChannelTabs.VIDEOS, extractor.getName());
32+
}
33+
34+
@Test
35+
void testGetUrl() throws Exception {
36+
assertEquals("https://media.ccc.de/c/camp2023", extractor.getUrl());
37+
}
38+
39+
@Test
40+
void testGetOriginalUrl() throws Exception {
41+
assertEquals("https://media.ccc.de/c/camp2023", extractor.getOriginalUrl());
42+
}
43+
44+
@Test
45+
void testGetInitalPage() throws Exception {
46+
assertEquals(177, extractor.getInitialPage().getItems().size());
47+
}
48+
}
49+
}

extractor/src/test/java/org/schabi/newpipe/extractor/services/media_ccc/MediaCCCConferenceExtractorTest.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import static org.schabi.newpipe.extractor.ServiceList.MediaCCC;
1414

1515
/**
16-
* Test {@link MediaCCCConferenceExtractor}
16+
* Test {@link MediaCCCConferenceExtractor} and {@link
17+
* org.schabi.newpipe.extractor.services.media_ccc.extractors.MediaCCCChannelTabExtractor}
1718
*/
1819
public class MediaCCCConferenceExtractorTest {
1920
public static class FrOSCon2017 {

0 commit comments

Comments
 (0)