Skip to content

Commit 003dca6

Browse files
authored
Merge pull request #1476 from Ecomont/fix/soundcloud-comments-no-url
Fix Soundcloud comments crash by handling null page URLs
2 parents 1875788 + 6797137 commit 003dca6

12 files changed

Lines changed: 813 additions & 2 deletions

File tree

extractor/src/main/java/org/schabi/newpipe/extractor/services/soundcloud/extractors/SoundcloudCommentsExtractor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public InfoItemsPage<CommentsInfoItem> getInitialPage() throws ExtractionExcepti
4141
public InfoItemsPage<CommentsInfoItem> getPage(final Page page) throws ExtractionException,
4242
IOException {
4343
if (page == null || isNullOrEmpty(page.getUrl())) {
44-
throw new IllegalArgumentException("Page doesn't contain an URL");
44+
return InfoItemsPage.emptyPage();
4545
}
4646
return getPage(page.getUrl());
4747
}
@@ -63,7 +63,8 @@ private InfoItemsPage<CommentsInfoItem> getPage(@Nonnull final String url)
6363
getServiceId());
6464

6565
collectStreamsFrom(collector, json.getArray("collection"));
66-
return new InfoItemsPage<>(collector, new Page(json.getString("next_href", null)));
66+
final String nextHref = json.getString("next_href");
67+
return new InfoItemsPage<>(collector, isNullOrEmpty(nextHref) ? null : new Page(nextHref));
6768
}
6869

6970
@Override
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package org.schabi.newpipe.extractor.services.soundcloud;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
import static org.schabi.newpipe.extractor.ServiceList.SoundCloud;
6+
7+
import org.junit.jupiter.api.Nested;
8+
import org.junit.jupiter.api.Test;
9+
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
10+
import org.schabi.newpipe.extractor.Page;
11+
import org.schabi.newpipe.extractor.comments.CommentsExtractor;
12+
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
13+
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
14+
import org.schabi.newpipe.extractor.services.DefaultSimpleExtractorTest;
15+
16+
import java.io.IOException;
17+
18+
public class SoundcloudCommentsExtractorTest {
19+
20+
/**
21+
* Regression test for <a href="https://github.com/TeamNewPipe/NewPipeExtractor/issues/1243">
22+
* issue #1243</a>: when the SoundCloud API returns {@code "next_href": null} (no more pages),
23+
* a subsequent call to {@link CommentsExtractor#getPage(Page)} with a null URL must not throw
24+
* {@link IllegalArgumentException} ("Page doesn't contain an URL"). Instead the extractor
25+
* must return {@link InfoItemsPage#emptyPage()}.
26+
*
27+
* <p>The crash manifests during pagination: the last page of comments stores
28+
* {@code new Page(null)} as the next page, and when Paging 3 tries to fetch it the
29+
* exception propagates and kills the app.</p>
30+
*/
31+
@Nested
32+
class TrackWithComments extends DefaultSimpleExtractorTest<CommentsExtractor> {
33+
// This track is known to reproduce issue #1243: it has comments, but when pagination
34+
// exhausts the pages the API returns next_href=null, which previously caused a crash.
35+
private static final String URL = "https://soundcloud.com/user-722618400/a-real-playa";
36+
37+
@Override
38+
protected CommentsExtractor createExtractor() throws Exception {
39+
return SoundCloud.getCommentsExtractor(URL);
40+
}
41+
42+
/**
43+
* The initial page must load successfully without throwing any exception.
44+
*/
45+
@Test
46+
void testGetInitialPageSucceeds() throws IOException, ExtractionException {
47+
final InfoItemsPage<CommentsInfoItem> page = extractor().getInitialPage();
48+
// The track has comments; we only assert the call itself does not throw
49+
// and that the result is a valid (non-null) page.
50+
assertTrue(page.getErrors().isEmpty(),
51+
"Expected no extractor errors on initial page");
52+
}
53+
54+
/**
55+
* Regression test for issue #1243: calling {@link CommentsExtractor#getPage(Page)} with a
56+
* {@link Page} whose URL is null (which is what gets stored when {@code next_href} is
57+
* absent in the API response) must return {@link InfoItemsPage#emptyPage()} rather than
58+
* throw {@link IllegalArgumentException}.
59+
*/
60+
@Test
61+
void testGetPageWithNullUrlReturnsEmptyPage() throws IOException, ExtractionException {
62+
final InfoItemsPage<CommentsInfoItem> page = extractor().getPage(new Page((String) null));
63+
assertTrue(page.getItems().isEmpty(),
64+
"Expected empty items when page URL is null");
65+
assertFalse(page.hasNextPage(),
66+
"Expected no next page when page URL is null");
67+
}
68+
}
69+
70+
/**
71+
* Tests a SoundCloud track that has no comments.
72+
*
73+
* <p>Verifies that the extractor handles an empty collection gracefully:
74+
* the initial page must load without error, return no items, and have no next page.</p>
75+
*/
76+
@Nested
77+
class TrackWithNoComments extends DefaultSimpleExtractorTest<CommentsExtractor> {
78+
private static final String URL = "https://soundcloud.com/user285130010/jdkskls";
79+
80+
@Override
81+
protected CommentsExtractor createExtractor() throws Exception {
82+
return SoundCloud.getCommentsExtractor(URL);
83+
}
84+
85+
/**
86+
* The initial page must load successfully, return an empty items list,
87+
* and report no next page.
88+
*/
89+
@Test
90+
void testGetInitialPageIsEmpty() throws IOException, ExtractionException {
91+
final InfoItemsPage<CommentsInfoItem> page = extractor().getInitialPage();
92+
assertTrue(page.getErrors().isEmpty(),
93+
"Expected no extractor errors on initial page");
94+
assertTrue(page.getItems().isEmpty(),
95+
"Expected no comments for a track with no comments");
96+
assertFalse(page.hasNextPage(),
97+
"Expected no next page for a track with no comments");
98+
}
99+
}
100+
}

extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/soundcloud/soundcloudcommentsextractor/trackwithcomments/generated_mock_0.json

Lines changed: 68 additions & 0 deletions
Large diffs are not rendered by default.

extractor/src/test/resources/mocks/v1/org/schabi/newpipe/extractor/services/soundcloud/soundcloudcommentsextractor/trackwithcomments/generated_mock_1.json

Lines changed: 83 additions & 0 deletions
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"request": {
3+
"httpMethod": "GET",
4+
"url": "https://api-widget.soundcloud.com/resolve?url\u003dhttps%3A%2F%2Fsoundcloud.com%2Fuser-722618400%2Fa-real-playa\u0026format\u003djson\u0026client_id\u003dCkCiIyf14rHi27fhk7HxhPOzc85okfSJ",
5+
"headers": {
6+
"Accept-Language": [
7+
"en-GB, en;q\u003d0.9"
8+
]
9+
},
10+
"localization": {
11+
"languageCode": "en",
12+
"countryCode": "GB"
13+
}
14+
},
15+
"response": {
16+
"responseCode": 200,
17+
"responseMessage": "OK",
18+
"responseHeaders": {
19+
"cache-control": [
20+
"private, max-age\u003d0"
21+
],
22+
"connection": [
23+
"keep-alive"
24+
],
25+
"content-type": [
26+
"application/json; charset\u003dutf-8"
27+
],
28+
"date": [
29+
"Tue, 14 Apr 2026 03:30:30 GMT"
30+
],
31+
"referrer-policy": [
32+
"no-referrer"
33+
],
34+
"server": [
35+
"am/2"
36+
],
37+
"strict-transport-security": [
38+
"max-age\u003d63072000"
39+
],
40+
"vary": [
41+
"Origin"
42+
],
43+
"via": [
44+
"1.1 a21dc4de5833aaa6d917631becb22680.cloudfront.net (CloudFront)"
45+
],
46+
"x-amz-cf-id": [
47+
"HJt_lcobUD2i62Zgo_Olkgeo21xHnG_Ri6ezzh6eWmY94QBsuKjIJA\u003d\u003d"
48+
],
49+
"x-amz-cf-pop": [
50+
"MAD56-P2"
51+
],
52+
"x-cache": [
53+
"Miss from cloudfront"
54+
],
55+
"x-content-type-options": [
56+
"nosniff"
57+
],
58+
"x-frame-options": [
59+
"DENY"
60+
],
61+
"x-robots-tag": [
62+
"noindex"
63+
]
64+
},
65+
"responseBody": "{\"artwork_url\":\"https://i1.sndcdn.com/artworks-te4SwNsMypkflClB-xRgdyA-large.jpg\",\"caption\":null,\"commentable\":true,\"comment_count\":8,\"created_at\":\"2023-11-20T17:45:43Z\",\"description\":\"\",\"downloadable\":false,\"download_count\":0,\"duration\":205937,\"full_duration\":205923,\"embeddable_by\":\"all\",\"genre\":\"\",\"has_downloads_left\":false,\"id\":1670147625,\"kind\":\"track\",\"label_name\":null,\"last_modified\":\"2023-11-20T17:45:50Z\",\"license\":\"all-rights-reserved\",\"likes_count\":4724,\"permalink\":\"a-real-playa\",\"permalink_url\":\"https://soundcloud.com/user-722618400/a-real-playa\",\"playback_count\":486999,\"public\":true,\"publisher_metadata\":null,\"purchase_title\":null,\"purchase_url\":null,\"release_date\":null,\"reposts_count\":25,\"secret_token\":null,\"sharing\":\"public\",\"state\":\"finished\",\"streamable\":true,\"tag_list\":\"\",\"title\":\"A real playa\",\"uri\":\"https://api.soundcloud.com/tracks/soundcloud%3Atracks%3A1670147625\",\"urn\":\"soundcloud:tracks:1670147625\",\"user_id\":910824574,\"visuals\":null,\"waveform_url\":\"https://wave.sndcdn.com/a2nCRfj9JoLO_m.json\",\"display_date\":\"2023-11-20T17:45:43Z\",\"media\":{\"transcodings\":[{\"url\":\"https://api-widget.soundcloud.com/media/soundcloud:tracks:1670147625/872aed92-cbc3-41bf-b6c2-9cede4bd733d/stream/hls\",\"preset\":\"abr_sq\",\"duration\":205937,\"snipped\":false,\"format\":{\"protocol\":\"hls\",\"mime_type\":\"audio/mpegurl\"},\"quality\":\"sq\",\"is_legacy_transcoding\":false},{\"url\":\"https://api-widget.soundcloud.com/media/soundcloud:tracks:1670147625/3d877120-2536-439d-b511-1162dd39339c/stream/hls\",\"preset\":\"mp3_1_0\",\"duration\":205923,\"snipped\":false,\"format\":{\"protocol\":\"hls\",\"mime_type\":\"audio/mpeg\"},\"quality\":\"sq\",\"is_legacy_transcoding\":true},{\"url\":\"https://api-widget.soundcloud.com/media/soundcloud:tracks:1670147625/3d877120-2536-439d-b511-1162dd39339c/stream/progressive\",\"preset\":\"mp3_1_0\",\"duration\":205923,\"snipped\":false,\"format\":{\"protocol\":\"progressive\",\"mime_type\":\"audio/mpeg\"},\"quality\":\"sq\",\"is_legacy_transcoding\":true}]},\"station_urn\":\"soundcloud:system-playlists:track-stations:1670147625\",\"station_permalink\":\"track-stations:1670147625\",\"track_authorization\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnZW8iOiJFUyIsInN1YiI6IiIsInJpZCI6IjViMDU5NmQyLTc4NjgtNDU3OS1iYTcxLWNiYjUxNDA4YWVmYyIsImlhdCI6MTc3NjEzNzQzMX0.gAPqWZGukOShIAgkgAadCnTeHeISq7Be-CdblFzqdyg\",\"monetization_model\":\"BLACKBOX\",\"policy\":\"MONETIZE\",\"user\":{\"avatar_url\":\"https://i1.sndcdn.com/avatars-SBGwwERPMyiSuj8W-yMJAGw-large.jpg\",\"city\":\"\",\"comments_count\":0,\"country_code\":null,\"created_at\":null,\"creator_subscriptions\":[{\"product\":{\"id\":\"free\"}}],\"creator_subscription\":{\"product\":{\"id\":\"free\"}},\"description\":\"Banagher, Co.offaly\",\"followers_count\":240,\"followings_count\":66,\"first_name\":\"\",\"full_name\":\"\",\"groups_count\":0,\"id\":910824574,\"kind\":\"user\",\"last_modified\":\"2022-10-12T13:31:30Z\",\"last_name\":\"\",\"likes_count\":1193,\"playlist_likes_count\":11,\"permalink\":\"user-722618400\",\"permalink_url\":\"https://soundcloud.com/user-722618400\",\"playlist_count\":0,\"reposts_count\":null,\"track_count\":11,\"uri\":\"https://api.soundcloud.com/users/soundcloud%3Ausers%3A910824574\",\"urn\":\"soundcloud:users:910824574\",\"username\":\"Korbin Dolan\",\"verified\":false,\"visuals\":{\"urn\":\"soundcloud:users:910824574\",\"enabled\":true,\"visuals\":[{\"urn\":\"soundcloud:visuals:167104191\",\"entry_time\":0,\"visual_url\":\"https://i1.sndcdn.com/visuals-CSbB5vvPqVaPwNHe-gxbM8A-original.jpg\"}],\"tracking\":null},\"badges\":{\"pro\":false,\"creator_mid_tier\":false,\"pro_unlimited\":false,\"verified\":false},\"station_urn\":\"soundcloud:system-playlists:artist-stations:910824574\",\"station_permalink\":\"artist-stations:910824574\",\"date_of_birth\":null}}",
66+
"latestUrl": "https://api-widget.soundcloud.com/resolve?url\u003dhttps%3A%2F%2Fsoundcloud.com%2Fuser-722618400%2Fa-real-playa\u0026format\u003djson\u0026client_id\u003dCkCiIyf14rHi27fhk7HxhPOzc85okfSJ"
67+
}
68+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
{
2+
"request": {
3+
"httpMethod": "GET",
4+
"url": "https://api-widget.soundcloud.com/resolve?url\u003dhttps%3A%2F%2Fsoundcloud.com%2Fuser-722618400%2Fa-real-playa\u0026format\u003djson\u0026client_id\u003dCkCiIyf14rHi27fhk7HxhPOzc85okfSJ",
5+
"headers": {
6+
"Accept-Language": [
7+
"en-GB, en;q\u003d0.9"
8+
]
9+
},
10+
"localization": {
11+
"languageCode": "en",
12+
"countryCode": "GB"
13+
}
14+
},
15+
"response": {
16+
"responseCode": 200,
17+
"responseMessage": "OK",
18+
"responseHeaders": {
19+
"cache-control": [
20+
"private, max-age\u003d0"
21+
],
22+
"connection": [
23+
"keep-alive"
24+
],
25+
"content-type": [
26+
"application/json; charset\u003dutf-8"
27+
],
28+
"date": [
29+
"Tue, 14 Apr 2026 03:30:31 GMT"
30+
],
31+
"referrer-policy": [
32+
"no-referrer"
33+
],
34+
"server": [
35+
"am/2"
36+
],
37+
"strict-transport-security": [
38+
"max-age\u003d63072000"
39+
],
40+
"vary": [
41+
"Origin"
42+
],
43+
"via": [
44+
"1.1 a21dc4de5833aaa6d917631becb22680.cloudfront.net (CloudFront)"
45+
],
46+
"x-amz-cf-id": [
47+
"qxUO1kM0zTcFksoTmE_TnjMNMHQrd9kdBLkCVWoiNxxuSLETVHEw4A\u003d\u003d"
48+
],
49+
"x-amz-cf-pop": [
50+
"MAD56-P2"
51+
],
52+
"x-cache": [
53+
"Miss from cloudfront"
54+
],
55+
"x-content-type-options": [
56+
"nosniff"
57+
],
58+
"x-frame-options": [
59+
"DENY"
60+
],
61+
"x-robots-tag": [
62+
"noindex"
63+
]
64+
},
65+
"responseBody": "{\"artwork_url\":\"https://i1.sndcdn.com/artworks-te4SwNsMypkflClB-xRgdyA-large.jpg\",\"caption\":null,\"commentable\":true,\"comment_count\":8,\"created_at\":\"2023-11-20T17:45:43Z\",\"description\":\"\",\"downloadable\":false,\"download_count\":0,\"duration\":205937,\"full_duration\":205923,\"embeddable_by\":\"all\",\"genre\":\"\",\"has_downloads_left\":false,\"id\":1670147625,\"kind\":\"track\",\"label_name\":null,\"last_modified\":\"2023-11-20T17:45:50Z\",\"license\":\"all-rights-reserved\",\"likes_count\":4724,\"permalink\":\"a-real-playa\",\"permalink_url\":\"https://soundcloud.com/user-722618400/a-real-playa\",\"playback_count\":486999,\"public\":true,\"publisher_metadata\":null,\"purchase_title\":null,\"purchase_url\":null,\"release_date\":null,\"reposts_count\":25,\"secret_token\":null,\"sharing\":\"public\",\"state\":\"finished\",\"streamable\":true,\"tag_list\":\"\",\"title\":\"A real playa\",\"uri\":\"https://api.soundcloud.com/tracks/soundcloud%3Atracks%3A1670147625\",\"urn\":\"soundcloud:tracks:1670147625\",\"user_id\":910824574,\"visuals\":null,\"waveform_url\":\"https://wave.sndcdn.com/a2nCRfj9JoLO_m.json\",\"display_date\":\"2023-11-20T17:45:43Z\",\"media\":{\"transcodings\":[{\"url\":\"https://api-widget.soundcloud.com/media/soundcloud:tracks:1670147625/872aed92-cbc3-41bf-b6c2-9cede4bd733d/stream/hls\",\"preset\":\"abr_sq\",\"duration\":205937,\"snipped\":false,\"format\":{\"protocol\":\"hls\",\"mime_type\":\"audio/mpegurl\"},\"quality\":\"sq\",\"is_legacy_transcoding\":false},{\"url\":\"https://api-widget.soundcloud.com/media/soundcloud:tracks:1670147625/3d877120-2536-439d-b511-1162dd39339c/stream/hls\",\"preset\":\"mp3_1_0\",\"duration\":205923,\"snipped\":false,\"format\":{\"protocol\":\"hls\",\"mime_type\":\"audio/mpeg\"},\"quality\":\"sq\",\"is_legacy_transcoding\":true},{\"url\":\"https://api-widget.soundcloud.com/media/soundcloud:tracks:1670147625/3d877120-2536-439d-b511-1162dd39339c/stream/progressive\",\"preset\":\"mp3_1_0\",\"duration\":205923,\"snipped\":false,\"format\":{\"protocol\":\"progressive\",\"mime_type\":\"audio/mpeg\"},\"quality\":\"sq\",\"is_legacy_transcoding\":true}]},\"station_urn\":\"soundcloud:system-playlists:track-stations:1670147625\",\"station_permalink\":\"track-stations:1670147625\",\"track_authorization\":\"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJnZW8iOiJFUyIsInN1YiI6IiIsInJpZCI6IjViMDU5NmQyLTc4NjgtNDU3OS1iYTcxLWNiYjUxNDA4YWVmYyIsImlhdCI6MTc3NjEzNzQzMX0.gAPqWZGukOShIAgkgAadCnTeHeISq7Be-CdblFzqdyg\",\"monetization_model\":\"BLACKBOX\",\"policy\":\"MONETIZE\",\"user\":{\"avatar_url\":\"https://i1.sndcdn.com/avatars-SBGwwERPMyiSuj8W-yMJAGw-large.jpg\",\"city\":\"\",\"comments_count\":0,\"country_code\":null,\"created_at\":null,\"creator_subscriptions\":[{\"product\":{\"id\":\"free\"}}],\"creator_subscription\":{\"product\":{\"id\":\"free\"}},\"description\":\"Banagher, Co.offaly\",\"followers_count\":240,\"followings_count\":66,\"first_name\":\"\",\"full_name\":\"\",\"groups_count\":0,\"id\":910824574,\"kind\":\"user\",\"last_modified\":\"2022-10-12T13:31:30Z\",\"last_name\":\"\",\"likes_count\":1193,\"playlist_likes_count\":11,\"permalink\":\"user-722618400\",\"permalink_url\":\"https://soundcloud.com/user-722618400\",\"playlist_count\":0,\"reposts_count\":null,\"track_count\":11,\"uri\":\"https://api.soundcloud.com/users/soundcloud%3Ausers%3A910824574\",\"urn\":\"soundcloud:users:910824574\",\"username\":\"Korbin Dolan\",\"verified\":false,\"visuals\":{\"urn\":\"soundcloud:users:910824574\",\"enabled\":true,\"visuals\":[{\"urn\":\"soundcloud:visuals:167104191\",\"entry_time\":0,\"visual_url\":\"https://i1.sndcdn.com/visuals-CSbB5vvPqVaPwNHe-gxbM8A-original.jpg\"}],\"tracking\":null},\"badges\":{\"pro\":false,\"creator_mid_tier\":false,\"pro_unlimited\":false,\"verified\":false},\"station_urn\":\"soundcloud:system-playlists:artist-stations:910824574\",\"station_permalink\":\"artist-stations:910824574\",\"date_of_birth\":null}}",
66+
"latestUrl": "https://api-widget.soundcloud.com/resolve?url\u003dhttps%3A%2F%2Fsoundcloud.com%2Fuser-722618400%2Fa-real-playa\u0026format\u003djson\u0026client_id\u003dCkCiIyf14rHi27fhk7HxhPOzc85okfSJ"
67+
}
68+
}

0 commit comments

Comments
 (0)