From 832dfdaeb71cc6cdd0fe8813c02e033f2293bec2 Mon Sep 17 00:00:00 2001 From: polymorphicshade Date: Sat, 20 Jan 2024 17:52:53 -0700 Subject: [PATCH 01/14] added extractor components for SponsorBlock --- .../sponsorblock/SponsorBlockAction.java | 27 +++ .../sponsorblock/SponsorBlockApiSettings.java | 14 ++ .../sponsorblock/SponsorBlockCategory.java | 49 +++++ .../SponsorBlockExtractorHelper.java | 199 ++++++++++++++++++ .../sponsorblock/SponsorBlockSegment.java | 29 +++ .../newpipe/extractor/stream/StreamInfo.java | 65 +++++- .../schabi/newpipe/extractor/utils/Utils.java | 20 ++ 7 files changed, 397 insertions(+), 6 deletions(-) create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockAction.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockApiSettings.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockCategory.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockExtractorHelper.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockSegment.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockAction.java b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockAction.java new file mode 100644 index 000000000..395fd832e --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockAction.java @@ -0,0 +1,27 @@ +package org.schabi.newpipe.extractor.sponsorblock; + +public enum SponsorBlockAction { + SKIP("skip"), + POI("poi"); + + private final String apiName; + + SponsorBlockAction(final String apiName) { + this.apiName = apiName; + } + + public static SponsorBlockAction fromApiName(final String apiName) { + switch (apiName) { + case "skip": + return SponsorBlockAction.SKIP; + case "poi": + return SponsorBlockAction.POI; + default: + throw new IllegalArgumentException("Invalid API name"); + } + } + + public String getApiName() { + return apiName; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockApiSettings.java b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockApiSettings.java new file mode 100644 index 000000000..9693ba854 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockApiSettings.java @@ -0,0 +1,14 @@ +package org.schabi.newpipe.extractor.sponsorblock; + +public class SponsorBlockApiSettings { + public String apiUrl; + public boolean includeSponsorCategory; + public boolean includeIntroCategory; + public boolean includeOutroCategory; + public boolean includeInteractionCategory; + public boolean includeHighlightCategory; + public boolean includeSelfPromoCategory; + public boolean includeMusicCategory; + public boolean includePreviewCategory; + public boolean includeFillerCategory; +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockCategory.java b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockCategory.java new file mode 100644 index 000000000..55a78972d --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockCategory.java @@ -0,0 +1,49 @@ +package org.schabi.newpipe.extractor.sponsorblock; + +public enum SponsorBlockCategory { + SPONSOR("sponsor"), + INTRO("intro"), + OUTRO("outro"), + INTERACTION("interaction"), + HIGHLIGHT("poi_highlight"), + SELF_PROMO("selfpromo"), + NON_MUSIC("music_offtopic"), + PREVIEW("preview"), + FILLER("filler"), + PENDING("pending"); + + private final String apiName; + + SponsorBlockCategory(final String apiName) { + this.apiName = apiName; + } + + public static SponsorBlockCategory fromApiName(final String apiName) { + switch (apiName) { + case "sponsor": + return SponsorBlockCategory.SPONSOR; + case "intro": + return SponsorBlockCategory.INTRO; + case "outro": + return SponsorBlockCategory.OUTRO; + case "interaction": + return SponsorBlockCategory.INTERACTION; + case "poi_highlight": + return SponsorBlockCategory.HIGHLIGHT; + case "selfpromo": + return SponsorBlockCategory.SELF_PROMO; + case "music_offtopic": + return SponsorBlockCategory.NON_MUSIC; + case "preview": + return SponsorBlockCategory.PREVIEW; + case "filler": + return SponsorBlockCategory.FILLER; + default: + throw new IllegalArgumentException("Invalid API name"); + } + } + + public String getApiName() { + return apiName; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockExtractorHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockExtractorHelper.java new file mode 100644 index 000000000..9e5234358 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockExtractorHelper.java @@ -0,0 +1,199 @@ +package org.schabi.newpipe.extractor.sponsorblock; + +import com.grack.nanojson.JsonArray; +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.downloader.Response; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.extractor.utils.RandomStringFromAlphabetGenerator; +import org.schabi.newpipe.extractor.utils.Utils; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.Random; + +public final class SponsorBlockExtractorHelper { + private static final String ALPHABET = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + private static final Random NUMBER_GENERATOR = new SecureRandom(); + + private SponsorBlockExtractorHelper() { + } + + public static SponsorBlockSegment[] getSegments(final StreamInfo streamInfo, + final SponsorBlockApiSettings apiSettings) + throws UnsupportedEncodingException { + if (!streamInfo.getUrl().startsWith("https://www.youtube.com") + || apiSettings.apiUrl == null + || apiSettings.apiUrl.isEmpty()) { + return new SponsorBlockSegment[0]; + } + + final String videoId = streamInfo.getId(); + + final ArrayList categoryParamList = new ArrayList<>(); + + if (apiSettings.includeSponsorCategory) { + categoryParamList.add(SponsorBlockCategory.SPONSOR.getApiName()); + } + if (apiSettings.includeIntroCategory) { + categoryParamList.add(SponsorBlockCategory.INTRO.getApiName()); + } + if (apiSettings.includeOutroCategory) { + categoryParamList.add(SponsorBlockCategory.OUTRO.getApiName()); + } + if (apiSettings.includeInteractionCategory) { + categoryParamList.add(SponsorBlockCategory.INTERACTION.getApiName()); + } + if (apiSettings.includeHighlightCategory) { + categoryParamList.add(SponsorBlockCategory.HIGHLIGHT.getApiName()); + } + if (apiSettings.includeSelfPromoCategory) { + categoryParamList.add(SponsorBlockCategory.SELF_PROMO.getApiName()); + } + if (apiSettings.includeMusicCategory) { + categoryParamList.add(SponsorBlockCategory.NON_MUSIC.getApiName()); + } + if (apiSettings.includePreviewCategory) { + categoryParamList.add(SponsorBlockCategory.PREVIEW.getApiName()); + } + + if (apiSettings.includeFillerCategory) { + categoryParamList.add(SponsorBlockCategory.FILLER.getApiName()); + } + + if (categoryParamList.size() == 0) { + return new SponsorBlockSegment[0]; + } + + final String categoryParams = Utils.encodeUrlUtf8( + "[\"" + String.join("\",\"", categoryParamList) + "\"]"); + + final String actionParams = Utils.encodeUrlUtf8("[\"skip\",\"poi\"]"); + + final String videoIdHash; + try { + videoIdHash = Utils.toSha256(videoId); + } catch (NoSuchAlgorithmException e) { + return new SponsorBlockSegment[0]; + } + + final String url = apiSettings.apiUrl + "skipSegments/" + videoIdHash.substring(0, 4) + + "?categories=" + categoryParams + + "&actionTypes=" + actionParams + + "&userAgent=Mozilla/5.0"; + + JsonArray responseArray = null; + + try { + final String responseBody = NewPipe.getDownloader().get(url).responseBody(); + + responseArray = JsonParser.array().from(responseBody); + } catch (ReCaptchaException | IOException | JsonParserException e) { + // ignored + } + + if (responseArray == null) { + return new SponsorBlockSegment[0]; + } + + final ArrayList result = new ArrayList<>(); + + for (final Object obj1 : responseArray) { + final JsonObject jObj1 = (JsonObject) obj1; + + final String responseVideoId = jObj1.getString("videoID"); + if (!responseVideoId.equals(videoId)) { + continue; + } + + final JsonArray segmentArray = (JsonArray) jObj1.get("segments"); + if (segmentArray == null) { + continue; + } + + for (final Object obj2 : segmentArray) { + final JsonObject jObj2 = (JsonObject) obj2; + + final JsonArray segmentInfo = (JsonArray) jObj2.get("segment"); + if (segmentInfo == null) { + continue; + } + + final String uuid = jObj2.getString("UUID"); + final double startTime = segmentInfo.getDouble(0) * 1000; + final double endTime = segmentInfo.getDouble(1) * 1000; + final String category = jObj2.getString("category"); + final String action = jObj2.getString("actionType"); + + final SponsorBlockSegment sponsorBlockSegment = + new SponsorBlockSegment(uuid, startTime, endTime, + SponsorBlockCategory.fromApiName(category), + SponsorBlockAction.fromApiName(action)); + result.add(sponsorBlockSegment); + } + } + + return result.toArray(new SponsorBlockSegment[0]); + } + + public static Response submitSponsorBlockSegment( + final StreamInfo streamInfo, + final SponsorBlockSegment segment, + final String apiUrl) + throws IOException, ReCaptchaException { + if (segment.category == SponsorBlockCategory.PENDING) { + return null; + } + + if (!streamInfo.getUrl().startsWith("https://www.youtube.com")) { + return null; + } + + final String videoId = streamInfo.getId(); + + final String localUserId = + RandomStringFromAlphabetGenerator.generate(ALPHABET, 32, NUMBER_GENERATOR); + + final String actionType = segment.category == SponsorBlockCategory.HIGHLIGHT + ? "poi" + : "skip"; + + final double startInSeconds = segment.startTime / 1000.0; + final double endInSeconds = segment.category == SponsorBlockCategory.HIGHLIGHT + ? startInSeconds + : segment.endTime / 1000.0; + + final String url = apiUrl + "skipSegments?" + + "videoID=" + videoId + + "&startTime=" + startInSeconds + + "&endTime=" + endInSeconds + + "&category=" + segment.category.getApiName() + + "&userID=" + localUserId + + "&userAgent=Mozilla/5.0" + + "&actionType=" + actionType; + return NewPipe.getDownloader().post(url, null, new byte[0]); + } + + public static Response submitSponsorBlockSegmentVote(final String uuid, + final String apiUrl, + final int vote) + throws IOException, ReCaptchaException { + final String localUserId = + RandomStringFromAlphabetGenerator.generate(ALPHABET, 32, NUMBER_GENERATOR); + + final String url = apiUrl + "voteOnSponsorTime?" + + "UUID=" + uuid + + "&userID=" + localUserId + + "&type=" + vote; + + return NewPipe.getDownloader().post(url, null, new byte[0]); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockSegment.java b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockSegment.java new file mode 100644 index 000000000..01f6c9c0c --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/sponsorblock/SponsorBlockSegment.java @@ -0,0 +1,29 @@ +package org.schabi.newpipe.extractor.sponsorblock; + +import java.io.Serializable; + +public class SponsorBlockSegment implements Serializable { + public String uuid; + public double startTime; + public double endTime; + public SponsorBlockCategory category; + public SponsorBlockAction action; + + public SponsorBlockSegment(final String uuid, final double startTime, final double endTime, + final SponsorBlockCategory category, + final SponsorBlockAction action) { + // NOTE: start/end times are in milliseconds + + this.uuid = uuid; + this.startTime = startTime; + this.endTime = endTime; + this.category = category; + this.action = action; + + // since "highlight" segments are marked with the same start time and end time, + // increment the end time by 1 second (so it is actually visible on the seekbar) + if (this.category == SponsorBlockCategory.HIGHLIGHT) { + this.endTime = this.startTime + 1000; + } + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java index b54c69afc..617299de4 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java @@ -30,13 +30,19 @@ import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.localization.DateWrapper; +import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockApiSettings; +import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockExtractorHelper; +import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment; import org.schabi.newpipe.extractor.utils.ExtractorHelper; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Locale; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty; @@ -63,16 +69,24 @@ public StreamInfo(final int serviceId, this.ageLimit = ageLimit; } - public static StreamInfo getInfo(final String url) throws IOException, ExtractionException { - return getInfo(NewPipe.getServiceByUrl(url), url); + public static StreamInfo getInfo( + final String url, + @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings) + throws IOException, ExtractionException { + return getInfo(NewPipe.getServiceByUrl(url), url, sponsorBlockApiSettings); } - public static StreamInfo getInfo(@Nonnull final StreamingService service, - final String url) throws IOException, ExtractionException { - return getInfo(service.getStreamExtractor(url)); + public static StreamInfo getInfo( + @Nonnull final StreamingService service, + final String url, + @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings) + throws IOException, ExtractionException { + return getInfo(service.getStreamExtractor(url), sponsorBlockApiSettings); } - public static StreamInfo getInfo(@Nonnull final StreamExtractor extractor) + public static StreamInfo getInfo( + @Nonnull final StreamExtractor extractor, + @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings) throws ExtractionException, IOException { extractor.fetchPage(); final StreamInfo streamInfo; @@ -80,6 +94,13 @@ public static StreamInfo getInfo(@Nonnull final StreamExtractor extractor) streamInfo = extractImportantData(extractor); extractStreams(streamInfo, extractor); extractOptionalData(streamInfo, extractor); + + if (sponsorBlockApiSettings != null) { + final SponsorBlockSegment[] sponsorBlockSegments = + SponsorBlockExtractorHelper.getSegments(streamInfo, sponsorBlockApiSettings); + streamInfo.setSponsorBlockSegments(sponsorBlockSegments); + } + return streamInfo; } catch (final ExtractionException e) { @@ -381,6 +402,7 @@ private static void extractOptionalData(final StreamInfo streamInfo, private List streamSegments = List.of(); private List metaInfo = List.of(); private boolean shortFormContent = false; + private List sponsorBlockSegments = new ArrayList<>(); /** * Preview frames, e.g. for the storyboard / seekbar thumbnail preview @@ -727,4 +749,35 @@ public boolean isShortFormContent() { public void setShortFormContent(final boolean isShortFormContent) { this.shortFormContent = isShortFormContent; } + + public SponsorBlockSegment[] getSponsorBlockSegments() { + return sponsorBlockSegments.toArray(new SponsorBlockSegment[0]); + } + + public void setSponsorBlockSegments(final SponsorBlockSegment[] sponsorBlockSegments) { + this.sponsorBlockSegments.clear(); + Collections.addAll(this.sponsorBlockSegments, sponsorBlockSegments); + } + + public void addSponsorBlockSegment(final SponsorBlockSegment sponsorBlockSegment) { + sponsorBlockSegments.add(sponsorBlockSegment); + } + + public void removeSponsorBlockSegment(final SponsorBlockSegment sponsorBlockSegment) { + sponsorBlockSegments.remove(sponsorBlockSegment); + } + + public void removeSponsorBlockSegment(final String uuid) { + SponsorBlockSegment target = null; + for (final SponsorBlockSegment segment : sponsorBlockSegments) { + if (segment.uuid.equals(uuid)) { + target = segment; + break; + } + } + + if (target != null) { + removeSponsorBlockSegment(target); + } + } } diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java index dd990c0ea..d853eb0ab 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Utils.java @@ -8,6 +8,8 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collection; import java.util.Map; @@ -417,4 +419,22 @@ public static String getStringResultFromRegexArray(@Nonnull final String input, throw new Parser.RegexException("No regex matched the input on group " + group); } + + public static String toSha256(final String videoId) throws NoSuchAlgorithmException { + final MessageDigest digest = MessageDigest.getInstance("SHA-256"); + final byte[] bytes = digest.digest(videoId.getBytes(StandardCharsets.UTF_8)); + final StringBuilder sb = new StringBuilder(); + + for (final byte b : bytes) { + final String hex = Integer.toHexString(0xff & b); + + if (hex.length() == 1) { + sb.append('0'); + } + + sb.append(hex); + } + + return sb.toString(); + } } From 3dd05edae2f6cfdc08bcdfb0924a86513a8f6792 Mon Sep 17 00:00:00 2001 From: polymorphicshade Date: Sat, 20 Jan 2024 17:53:22 -0700 Subject: [PATCH 02/14] added extractor components for ReturnYouTubeDislike --- .../ReturnYouTubeDislikeApiSettings.java | 9 ++++ .../ReturnYouTubeDislikeExtractorHelper.java | 53 +++++++++++++++++++ .../ReturnYouTubeDislikeInfo.java | 20 +++++++ .../newpipe/extractor/stream/StreamInfo.java | 33 ++++++++++-- 4 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeApiSettings.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeExtractorHelper.java create mode 100644 extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeInfo.java diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeApiSettings.java b/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeApiSettings.java new file mode 100644 index 000000000..9793b26fb --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeApiSettings.java @@ -0,0 +1,9 @@ +package org.schabi.newpipe.extractor.returnyoutubedislike; + +public class ReturnYouTubeDislikeApiSettings { + public String apiUrl; + + // TODO: add more if needed + // + // +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeExtractorHelper.java b/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeExtractorHelper.java new file mode 100644 index 000000000..c43343209 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeExtractorHelper.java @@ -0,0 +1,53 @@ +package org.schabi.newpipe.extractor.returnyoutubedislike; + +import com.grack.nanojson.JsonObject; +import com.grack.nanojson.JsonParser; +import com.grack.nanojson.JsonParserException; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; +import org.schabi.newpipe.extractor.stream.StreamInfo; + +import java.io.IOException; + +public final class ReturnYouTubeDislikeExtractorHelper { + + private ReturnYouTubeDislikeExtractorHelper() { + } + + @SuppressWarnings("CheckStyle") + public static ReturnYouTubeDislikeInfo getInfo( + final StreamInfo streamInfo, + final ReturnYouTubeDislikeApiSettings apiSettings) { + if (!streamInfo.getUrl().startsWith("https://www.youtube.com") + || apiSettings.apiUrl == null + || apiSettings.apiUrl.isEmpty()) { + return null; + } + + final String url = apiSettings.apiUrl + "votes?videoId=" + streamInfo.getId(); + + JsonObject response = null; + + try { + final String responseBody = + NewPipe.getDownloader().get(url).responseBody(); + + response = JsonParser.object().from(responseBody); + } catch (ReCaptchaException | IOException | JsonParserException e) { + // ignored + } + + if (response == null) { + return null; + } + + final int likes = response.getInt("likes", 0); + final int dislikes = response.getInt("dislikes", 0); + final double rating = response.getDouble("rating", 0); + final int viewCount = response.getInt("viewCount", 0); + final boolean deleted = response.getBoolean("deleted", false); + + return new ReturnYouTubeDislikeInfo(likes, dislikes, rating, viewCount, deleted); + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeInfo.java b/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeInfo.java new file mode 100644 index 000000000..5b47261d0 --- /dev/null +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/returnyoutubedislike/ReturnYouTubeDislikeInfo.java @@ -0,0 +1,20 @@ +package org.schabi.newpipe.extractor.returnyoutubedislike; + +import java.io.Serializable; + +public class ReturnYouTubeDislikeInfo implements Serializable { + public long likes; + public long dislikes; + public double rating; + public long viewCount; + public boolean deleted; + + public ReturnYouTubeDislikeInfo(final long likes, final long dislikes, final double rating, + final long viewCount, final boolean deleted) { + this.likes = likes; + this.dislikes = dislikes; + this.rating = rating; + this.viewCount = viewCount; + this.deleted = deleted; + } +} diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java index 617299de4..e85012c27 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/stream/StreamInfo.java @@ -33,6 +33,9 @@ import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockApiSettings; import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockExtractorHelper; import org.schabi.newpipe.extractor.sponsorblock.SponsorBlockSegment; +import org.schabi.newpipe.extractor.returnyoutubedislike.ReturnYouTubeDislikeApiSettings; +import org.schabi.newpipe.extractor.returnyoutubedislike.ReturnYouTubeDislikeExtractorHelper; +import org.schabi.newpipe.extractor.returnyoutubedislike.ReturnYouTubeDislikeInfo; import org.schabi.newpipe.extractor.utils.ExtractorHelper; import java.io.IOException; @@ -71,22 +74,25 @@ public StreamInfo(final int serviceId, public static StreamInfo getInfo( final String url, - @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings) + @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings, + @Nullable final ReturnYouTubeDislikeApiSettings returnYouTubeDislikeApiSettings) throws IOException, ExtractionException { - return getInfo(NewPipe.getServiceByUrl(url), url, sponsorBlockApiSettings); + return getInfo(NewPipe.getServiceByUrl(url), url, sponsorBlockApiSettings, returnYouTubeDislikeApiSettings); } public static StreamInfo getInfo( @Nonnull final StreamingService service, final String url, - @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings) + @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings, + @Nullable final ReturnYouTubeDislikeApiSettings returnYouTubeDislikeApiSettings) throws IOException, ExtractionException { - return getInfo(service.getStreamExtractor(url), sponsorBlockApiSettings); + return getInfo(service.getStreamExtractor(url), sponsorBlockApiSettings, returnYouTubeDislikeApiSettings); } public static StreamInfo getInfo( @Nonnull final StreamExtractor extractor, - @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings) + @Nullable final SponsorBlockApiSettings sponsorBlockApiSettings, + @Nullable final ReturnYouTubeDislikeApiSettings returnYouTubeDislikeApiSettings) throws ExtractionException, IOException { extractor.fetchPage(); final StreamInfo streamInfo; @@ -101,6 +107,13 @@ public static StreamInfo getInfo( streamInfo.setSponsorBlockSegments(sponsorBlockSegments); } + if (returnYouTubeDislikeApiSettings != null) { + final ReturnYouTubeDislikeInfo rydInfo = + ReturnYouTubeDislikeExtractorHelper.getInfo( + streamInfo, returnYouTubeDislikeApiSettings); + streamInfo.setReturnYouTubeDislikeInfo(rydInfo); + } + return streamInfo; } catch (final ExtractionException e) { @@ -403,6 +416,7 @@ private static void extractOptionalData(final StreamInfo streamInfo, private List metaInfo = List.of(); private boolean shortFormContent = false; private List sponsorBlockSegments = new ArrayList<>(); + @Nullable private ReturnYouTubeDislikeInfo rydInfo; /** * Preview frames, e.g. for the storyboard / seekbar thumbnail preview @@ -780,4 +794,13 @@ public void removeSponsorBlockSegment(final String uuid) { removeSponsorBlockSegment(target); } } + + @Nullable + public ReturnYouTubeDislikeInfo getRydInfo() { + return rydInfo; + } + + public void setReturnYouTubeDislikeInfo(final @Nullable ReturnYouTubeDislikeInfo rydInfo) { + this.rydInfo = rydInfo; + } } From 00577dc5910fbd73b65ea5d35c06fdf0cf9862d3 Mon Sep 17 00:00:00 2001 From: polymorphicshade Date: Sun, 21 Jan 2024 14:53:41 -0700 Subject: [PATCH 03/14] changed README.md --- README.md | 66 +++---------------------------------------------------- 1 file changed, 3 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 15bf169c3..0b2c8f6ce 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,5 @@ -# NewPipe Extractor - -[![CI](https://github.com/TeamNewPipe/NewPipeExtractor/actions/workflows/ci.yml/badge.svg?branch=dev&event=schedule)](https://github.com/TeamNewPipe/NewPipeExtractor/actions/workflows/ci.yml) [![JIT Pack Badge](https://jitpack.io/v/TeamNewPipe/NewPipeExtractor.svg)](https://jitpack.io/#TeamNewPipe/NewPipeExtractor) [JDoc](https://teamnewpipe.github.io/NewPipeExtractor/javadoc/) • [Documentation](https://teamnewpipe.github.io/documentation/) - -NewPipe Extractor is a library for extracting things from streaming sites. It is a core component of [NewPipe](https://github.com/TeamNewPipe/NewPipe), but could be used independently. - -## Usage - -NewPipe Extractor is available at JitPack's Maven repo. - -If you're using Gradle, you could add NewPipe Extractor as a dependency with the following steps: - -1. Add `maven { url 'https://jitpack.io' }` to the `repositories` in your `build.gradle`. -2. Add `implementation 'com.github.TeamNewPipe:NewPipeExtractor:INSERT_VERSION_HERE'` to the `dependencies` in your `build.gradle`. Replace `INSERT_VERSION_HERE` with the [latest release](https://github.com/TeamNewPipe/NewPipeExtractor/releases/latest). -3. If you are using tools to minimize your project, make sure to keep the files below, by e.g. adding the following lines to your proguard file: - ``` -## Rules for NewPipeExtractor --keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; } --keep class org.mozilla.javascript.** { *; } --keep class org.mozilla.classfile.ClassFileWriter --dontwarn org.mozilla.javascript.tools.** -``` - -**Note:** To use NewPipe Extractor in Android projects with a `minSdk` below 26, [API desugaring](https://developer.android.com/studio/write/java8-support#library-desugaring) is required. If the `minSdk` is below 19, the `desugar_jdk_libs_nio` artifact is required, which requires Android Gradle Plugin (AGP) version 7.4.0. - -### Testing changes - -To test changes quickly you can build the library locally. A good approach would be to add something like the following to your `settings.gradle`: - -```groovy -includeBuild('../NewPipeExtractor') { - dependencySubstitution { - substitute module('com.github.TeamNewPipe:NewPipeExtractor') with project(':extractor') - } -} -``` - -Another approach would be to use the local Maven repository, here's a gist of how to use it: - -1. Add `mavenLocal()` in your project `repositories` list (usually as the first entry to give priority above the others). -2. It's _recommended_ that you change the `version` of this library (e.g. `LOCAL_SNAPSHOT`). -3. Run gradle's `ìnstall` task to deploy this library to your local repository (using the wrapper, present in the root of this project: `./gradlew install`) -4. Change the dependency version used in your project to match the one you chose in step 2 (`implementation 'com.github.TeamNewPipe:NewPipeExtractor:LOCAL_SNAPSHOT'`) - -> Tip for Android Studio users: After you make changes and run the `install` task, use the menu option `File → "Sync with File System"` to refresh the library in your project. - -## Supported sites - -The following sites are currently supported: - -- YouTube -- SoundCloud -- media.ccc.de -- PeerTube (no P2P) -- Bandcamp +

Tubular Extractor

+

A fork of NewPipeExtractor, a library for extracting things from streaming sites. It is a core component of Tubular, but could be used independently. ## License - -[![GNU GPLv3 Image](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) - -NewPipe Extractor is Free Software: You can use, study share and improve it at your -will. Specifically you can redistribute and/or modify it under the terms of the -[GNU General Public License](https://www.gnu.org/licenses/gpl.html) as -published by the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +[![GNU GPLv3](https://www.gnu.org/graphics/gplv3-127x51.png)](https://www.gnu.org/licenses/gpl-3.0.en.html) From 0a35faa4426d138232aab8c76a92444320a8dfc1 Mon Sep 17 00:00:00 2001 From: polymorphicshade Date: Sun, 7 Apr 2024 14:39:36 -0600 Subject: [PATCH 04/14] removed .github folder --- .github/PULL_REQUEST_TEMPLATE.md | 3 -- .github/dependabot.yml | 12 -------- .github/workflows/ci.yml | 51 -------------------------------- .github/workflows/docs.yml | 38 ------------------------ 4 files changed, 104 deletions(-) delete mode 100644 .github/PULL_REQUEST_TEMPLATE.md delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/docs.yml diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 76ca39a5e..000000000 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,3 +0,0 @@ -- [ ] I carefully read the [contribution guidelines](https://github.com/TeamNewPipe/NewPipe/blob/HEAD/.github/CONTRIBUTING.md) and agree to them. -- [ ] I have tested the API against [NewPipe](https://github.com/TeamNewPipe/NewPipe). -- [ ] I agree to create a pull request for [NewPipe](https://github.com/TeamNewPipe/NewPipe) as soon as possible to make it compatible with the changed API. diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 6a99bde2a..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,12 +0,0 @@ -version: 2 -updates: - # Maintain dependencies for Gradle - - package-ecosystem: "gradle" - directory: "/" - schedule: - interval: "daily" - # Maintain dependencies for GitHub Actions - - package-ecosystem: "github-actions" - directory: "/" - schedule: - interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 5a5db2b91..000000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: CI - -on: - schedule: - # once per day - - cron: 0 0 * * * - push: - branches: - - dev - - master - pull_request: - -permissions: - contents: read - -jobs: - build-and-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: set up JDK 11 - uses: actions/setup-java@v4 - with: - java-version: '11' - distribution: 'temurin' - - - name: Cache Gradle dependencies - uses: actions/cache@v4 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - restore-keys: ${{ runner.os }}-gradle - - # See gradle file for difference between downloaders - - name: Build and run Tests - run: | - if [[ "$GITHUB_EVENT_NAME" == 'schedule' ]]; then - echo running with real downloader - ./gradlew check --stacktrace -Ddownloader=REAL - else - echo running with mock downloader - ./gradlew check --stacktrace -Ddownloader=MOCK - fi - - - name: Upload test reports when failure occurs - uses: actions/upload-artifact@v4 - if: failure() - with: - name: NewPipeExtractor-test-reports - path: extractor/build/reports/tests/test/** diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index aeb19ef54..000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: Build and deploy JavaDocs - -on: - push: - branches: - - master - -permissions: - # The generated docs are written to the `gh-pages` branch. - contents: write - -jobs: - build-and-deploy-docs: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: set up JDK 11 - uses: actions/setup-java@v4 - with: - java-version: '11' - distribution: 'temurin' - - - name: Cache Gradle dependencies - uses: actions/cache@v4 - with: - path: ~/.gradle/caches - key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }} - restore-keys: ${{ runner.os }}-gradle - - - name: Build JavaDocs - run: ./gradlew aggregatedJavadocs - - - name: Deploy JavaDocs - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./build/docs From f26e84d39f10b86e66b87840136b2466067b988b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 09:43:03 +0000 Subject: [PATCH 05/14] Bump com.github.spotbugs:spotbugs-annotations from 4.8.3 to 4.8.5 Bumps [com.github.spotbugs:spotbugs-annotations](https://github.com/spotbugs/spotbugs) from 4.8.3 to 4.8.5. - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.8.3...4.8.5) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-annotations dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 28268a64a..8c383c15b 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ allprojects { ext { nanojsonVersion = "1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" - spotbugsVersion = "4.8.3" + spotbugsVersion = "4.8.5" junitVersion = "5.10.2" checkstyleVersion = "10.4" } From c6da4004e211a39a7620ffaa4c3301e8d5c44437 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 09:43:05 +0000 Subject: [PATCH 06/14] Bump org.mozilla:rhino from 1.7.13 to 1.7.15 Bumps [org.mozilla:rhino](https://github.com/mozilla/rhino) from 1.7.13 to 1.7.15. - [Release notes](https://github.com/mozilla/rhino/releases) - [Changelog](https://github.com/mozilla/rhino/blob/master/RELEASE-NOTES.md) - [Commits](https://github.com/mozilla/rhino/commits) --- updated-dependencies: - dependency-name: org.mozilla:rhino dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- extractor/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extractor/build.gradle b/extractor/build.gradle index 0692120cc..7dd32f300 100644 --- a/extractor/build.gradle +++ b/extractor/build.gradle @@ -31,7 +31,7 @@ dependencies { // do not upgrade to 1.7.14, since in 1.7.14 Rhino uses the `SourceVersion` class, which is not // available on Android (even when using desugaring), and `NoClassDefFoundError` is thrown - implementation 'org.mozilla:rhino:1.7.13' + implementation 'org.mozilla:rhino:1.7.15' checkstyle "com.puppycrawl.tools:checkstyle:$checkstyleVersion" From e54f38f5e7ca083eb0af98dd862735dd9aeef8fc Mon Sep 17 00:00:00 2001 From: TobiGr Date: Wed, 8 May 2024 19:13:45 +0200 Subject: [PATCH 07/14] [PeerTube] Fix test UploaderName was changed by user --- .../services/soundcloud/SoundcloudPlaylistExtractorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractorTest.java index 38fe024e9..da51149df 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/soundcloud/SoundcloudPlaylistExtractorTest.java @@ -317,7 +317,7 @@ void testUploaderUrl() { @Test public void testUploaderName() { - assertEquals("user350509423", extractor.getUploaderName()); + assertEquals("Chaazyy", extractor.getUploaderName()); } @Test From 10c6965a2826471017ab2df25fdce0022e3cf973 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 17:16:08 +0000 Subject: [PATCH 08/14] Bump peaceiris/actions-gh-pages from 3 to 4 Bumps [peaceiris/actions-gh-pages](https://github.com/peaceiris/actions-gh-pages) from 3 to 4. - [Release notes](https://github.com/peaceiris/actions-gh-pages/releases) - [Changelog](https://github.com/peaceiris/actions-gh-pages/blob/main/CHANGELOG.md) - [Commits](https://github.com/peaceiris/actions-gh-pages/compare/v3...v4) --- updated-dependencies: - dependency-name: peaceiris/actions-gh-pages dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index aeb19ef54..01fc970a6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -32,7 +32,7 @@ jobs: run: ./gradlew aggregatedJavadocs - name: Deploy JavaDocs - uses: peaceiris/actions-gh-pages@v3 + uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./build/docs From 4f477ad72b25fa08b1d0b3e829072e8596e3a98d Mon Sep 17 00:00:00 2001 From: TobiGr Date: Wed, 8 May 2024 19:21:54 +0200 Subject: [PATCH 09/14] [PeerTube] Fix testing comment content The comment is not available anymore. --- .../services/peertube/PeertubeCommentsExtractorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeCommentsExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeCommentsExtractorTest.java index 3485aed78..2663102f9 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeCommentsExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeCommentsExtractorTest.java @@ -48,7 +48,7 @@ void testGetComments() throws IOException, ExtractionException { @Test void testGetCommentsFromCommentsInfo() throws IOException, ExtractionException { - final String comment = "Thanks for this nice video explanation of Peertube!"; + final String comment = "I love this. ❤"; final CommentsInfo commentsInfo = CommentsInfo.getInfo("https://framatube.org/w/kkGMgK9ZtnKfYAgnEtQxbv"); From fafd471606fd3657078e351d35b7fa8df5d73e4c Mon Sep 17 00:00:00 2001 From: TobiGr Date: Wed, 8 May 2024 19:24:03 +0200 Subject: [PATCH 10/14] [PeerTube] Fix test for like count Number changed --- .../services/peertube/PeertubeStreamExtractorTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeStreamExtractorTest.java b/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeStreamExtractorTest.java index b2c27d187..55c616e42 100644 --- a/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeStreamExtractorTest.java +++ b/extractor/src/test/java/org/schabi/newpipe/extractor/services/peertube/PeertubeStreamExtractorTest.java @@ -85,7 +85,7 @@ public void testGetLanguageInformation() throws ParsingException { @Override public long expectedViewCountAtLeast() { return 38600; } @Nullable @Override public String expectedUploadDate() { return "2018-10-01 10:52:46.396"; } @Nullable @Override public String expectedTextualUploadDate() { return "2018-10-01T10:52:46.396Z"; } - @Override public long expectedLikeCountAtLeast() { return 19; } + @Override public long expectedLikeCountAtLeast() { return 18; } @Override public long expectedDislikeCountAtLeast() { return 0; } @Override public String expectedHost() { return "framatube.org"; } @Override public String expectedCategory() { return "Science & Technology"; } From 2efea787d22831da7f220b00e2bfed952df6ef5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:35:16 +0000 Subject: [PATCH 11/14] Bump com.github.spotbugs:spotbugs-annotations from 4.8.5 to 4.8.6 Bumps [com.github.spotbugs:spotbugs-annotations](https://github.com/spotbugs/spotbugs) from 4.8.5 to 4.8.6. - [Release notes](https://github.com/spotbugs/spotbugs/releases) - [Changelog](https://github.com/spotbugs/spotbugs/blob/master/CHANGELOG.md) - [Commits](https://github.com/spotbugs/spotbugs/compare/4.8.5...4.8.6) --- updated-dependencies: - dependency-name: com.github.spotbugs:spotbugs-annotations dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 332e8202b..25054c6e7 100644 --- a/build.gradle +++ b/build.gradle @@ -28,7 +28,7 @@ allprojects { ext { nanojsonVersion = "1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" - spotbugsVersion = "4.8.5" + spotbugsVersion = "4.8.6" junitVersion = "5.10.2" checkstyleVersion = "10.4" } From 11a31721c55baad666f8ce53f0f3345a590b61b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:50:44 +0000 Subject: [PATCH 12/14] Bump org.junit:junit-bom from 5.10.2 to 5.10.3 Bumps [org.junit:junit-bom](https://github.com/junit-team/junit5) from 5.10.2 to 5.10.3. - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.2...r5.10.3) --- updated-dependencies: - dependency-name: org.junit:junit-bom dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 25054c6e7..065df11c5 100644 --- a/build.gradle +++ b/build.gradle @@ -29,7 +29,7 @@ allprojects { ext { nanojsonVersion = "1d9e1aea9049fc9f85e68b43ba39fe7be1c1f751" spotbugsVersion = "4.8.6" - junitVersion = "5.10.2" + junitVersion = "5.10.3" checkstyleVersion = "10.4" } } From 383000f10d68beb50dd57e155888b73d7ca51292 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 29 Jun 2024 09:22:41 +0000 Subject: [PATCH 13/14] Bump com.google.code.gson:gson from 2.10.1 to 2.11.0 Bumps [com.google.code.gson:gson](https://github.com/google/gson) from 2.10.1 to 2.11.0. - [Release notes](https://github.com/google/gson/releases) - [Changelog](https://github.com/google/gson/blob/main/CHANGELOG.md) - [Commits](https://github.com/google/gson/compare/gson-parent-2.10.1...gson-parent-2.11.0) --- updated-dependencies: - dependency-name: com.google.code.gson:gson dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- extractor/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extractor/build.gradle b/extractor/build.gradle index 7dd32f300..aab2494fe 100644 --- a/extractor/build.gradle +++ b/extractor/build.gradle @@ -41,5 +41,5 @@ dependencies { testImplementation 'org.junit.jupiter:junit-jupiter-params' testImplementation "com.squareup.okhttp3:okhttp:3.12.13" - testImplementation 'com.google.code.gson:gson:2.10.1' + testImplementation 'com.google.code.gson:gson:2.11.0' } From 592f1596e6341e1058378468152ba4221a0ddb69 Mon Sep 17 00:00:00 2001 From: XiangRongLin <41164160+XiangRongLin@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:20:33 +0200 Subject: [PATCH 14/14] [Youtube] Adjust throttling function extraction to changes (#1191) * [Youtube] Adjust throttling function extraction to changes --------- Co-authored-by: Stypox --- .../youtube/YoutubeSignatureUtils.java | 29 +++++------- .../YoutubeThrottlingParameterUtils.java | 45 +++++++++++++++---- .../newpipe/extractor/utils/Parser.java | 31 +++++++++++++ 3 files changed, 79 insertions(+), 26 deletions(-) diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSignatureUtils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSignatureUtils.java index 8e0567927..13365f5d6 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSignatureUtils.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSignatureUtils.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.extractor.services.youtube; +import static org.schabi.newpipe.extractor.utils.Parser.matchGroup1MultiplePatterns; + import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.utils.JavaScript; import org.schabi.newpipe.extractor.utils.Parser; @@ -20,13 +22,13 @@ final class YoutubeSignatureUtils { */ static final String DEOBFUSCATION_FUNCTION_NAME = "deobfuscate"; - private static final String[] FUNCTION_REGEXES = { - "\\bm=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)", - "\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)", + private static final Pattern[] FUNCTION_REGEXES = { // CHECKSTYLE:OFF - "(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)", + Pattern.compile("\\bm=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(h\\.s\\)\\)"), + Pattern.compile("\\bc&&\\(c=([a-zA-Z0-9$]{2,})\\(decodeURIComponent\\(c\\)\\)"), + Pattern.compile("(?:\\b|[^a-zA-Z0-9$])([a-zA-Z0-9$]{2,})\\s*=\\s*function\\(\\s*a\\s*\\)\\s*\\{\\s*a\\s*=\\s*a\\.split\\(\\s*\"\"\\s*\\)"), + Pattern.compile("([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(\"\"\\)\\s*;") // CHECKSTYLE:ON - "([\\w$]+)\\s*=\\s*function\\((\\w+)\\)\\{\\s*\\2=\\s*\\2\\.split\\(\"\"\\)\\s*;" }; private static final String STS_REGEX = "signatureTimestamp[=:](\\d+)"; @@ -104,19 +106,12 @@ static String getDeobfuscationCode(@Nonnull final String javaScriptPlayerCode) @Nonnull private static String getDeobfuscationFunctionName(@Nonnull final String javaScriptPlayerCode) throws ParsingException { - Parser.RegexException exception = null; - for (final String regex : FUNCTION_REGEXES) { - try { - return Parser.matchGroup1(regex, javaScriptPlayerCode); - } catch (final Parser.RegexException e) { - if (exception == null) { - exception = e; - } - } + try { + return matchGroup1MultiplePatterns(FUNCTION_REGEXES, javaScriptPlayerCode); + } catch (final Parser.RegexException e) { + throw new ParsingException( + "Could not find deobfuscation function with any of the known patterns", e); } - - throw new ParsingException( - "Could not find deobfuscation function with any of the known patterns", exception); } @Nonnull diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingParameterUtils.java b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingParameterUtils.java index c398c6202..5dae1c9bf 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingParameterUtils.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeThrottlingParameterUtils.java @@ -1,5 +1,7 @@ package org.schabi.newpipe.extractor.services.youtube; +import static org.schabi.newpipe.extractor.utils.Parser.matchMultiplePatterns; + import org.schabi.newpipe.extractor.exceptions.ParsingException; import org.schabi.newpipe.extractor.utils.JavaScript; import org.schabi.newpipe.extractor.utils.Parser; @@ -18,10 +20,33 @@ final class YoutubeThrottlingParameterUtils { private static final Pattern THROTTLING_PARAM_PATTERN = Pattern.compile("[&?]n=([^&]+)"); - private static final Pattern DEOBFUSCATION_FUNCTION_NAME_PATTERN = Pattern.compile( - // CHECKSTYLE:OFF - "\\.get\\(\"n\"\\)\\)&&\\([a-zA-Z0-9$_]=([a-zA-Z0-9$_]+)(?:\\[(\\d+)])?\\([a-zA-Z0-9$_]\\)"); - // CHECKSTYLE:ON + private static final String SINGLE_CHAR_VARIABLE_REGEX = "[a-zA-Z0-9$_]"; + + private static final String FUNCTION_NAME_REGEX = SINGLE_CHAR_VARIABLE_REGEX + "+"; + + private static final String ARRAY_ACCESS_REGEX = "\\[(\\d+)]"; + + /** + * The first regex matches this, where we want BDa: + *

+ * (b=String.fromCharCode(110),c=a.get(b))&&(c=BDa[0](c) + *

+ * Array access is optional, but needs to be handled, since the actual function is inside the + * array. + */ + // CHECKSTYLE:OFF + private static final Pattern[] DEOBFUSCATION_FUNCTION_NAME_REGEXES = { + Pattern.compile("\\(" + SINGLE_CHAR_VARIABLE_REGEX + "=String\\.fromCharCode\\(110\\)," + + SINGLE_CHAR_VARIABLE_REGEX + "=" + SINGLE_CHAR_VARIABLE_REGEX + "\\.get\\(" + + SINGLE_CHAR_VARIABLE_REGEX + "\\)\\)" + "&&\\(" + SINGLE_CHAR_VARIABLE_REGEX + + "=(" + FUNCTION_NAME_REGEX + ")" + "(?:" + ARRAY_ACCESS_REGEX + ")?\\(" + + SINGLE_CHAR_VARIABLE_REGEX + "\\)"), + Pattern.compile("\\.get\\(\"n\"\\)\\)&&\\(" + SINGLE_CHAR_VARIABLE_REGEX + + "=(" + FUNCTION_NAME_REGEX + ")(?:" + ARRAY_ACCESS_REGEX + ")?\\(" + + SINGLE_CHAR_VARIABLE_REGEX + "\\)"), + }; + // CHECKSTYLE:ON + // Escape the curly end brace to allow compatibility with Android's regex engine // See https://stackoverflow.com/q/45074813 @@ -48,11 +73,13 @@ private YoutubeThrottlingParameterUtils() { @Nonnull static String getDeobfuscationFunctionName(@Nonnull final String javaScriptPlayerCode) throws ParsingException { - final Matcher matcher = DEOBFUSCATION_FUNCTION_NAME_PATTERN.matcher(javaScriptPlayerCode); - if (!matcher.find()) { - throw new ParsingException("Failed to find deobfuscation function name pattern \"" - + DEOBFUSCATION_FUNCTION_NAME_PATTERN - + "\" in the base JavaScript player code"); + final Matcher matcher; + try { + matcher = matchMultiplePatterns(DEOBFUSCATION_FUNCTION_NAME_REGEXES, + javaScriptPlayerCode); + } catch (final Parser.RegexException e) { + throw new ParsingException("Could not find deobfuscation function with any of the " + + "known patterns in the base JavaScript player code", e); } final String functionName = matcher.group(1); diff --git a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Parser.java b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Parser.java index 6efc74f2d..cde9b3b62 100644 --- a/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Parser.java +++ b/extractor/src/main/java/org/schabi/newpipe/extractor/utils/Parser.java @@ -78,6 +78,37 @@ public static String matchGroup(@Nonnull final Pattern pat, } } + public static String matchGroup1MultiplePatterns(final Pattern[] patterns, final String input) + throws RegexException { + return matchMultiplePatterns(patterns, input).group(1); + } + + public static Matcher matchMultiplePatterns(final Pattern[] patterns, final String input) + throws RegexException { + Parser.RegexException exception = null; + for (final Pattern pattern : patterns) { + final Matcher matcher = pattern.matcher(input); + if (matcher.find()) { + return matcher; + } else if (exception == null) { + // only pass input to exception message when it is not too long + if (input.length() > 1024) { + exception = new RegexException("Failed to find pattern \"" + pattern.pattern() + + "\""); + } else { + exception = new RegexException("Failed to find pattern \"" + pattern.pattern() + + "\" inside of \"" + input + "\""); + } + } + } + + if (exception == null) { + throw new RegexException("Empty patterns array passed to matchMultiplePatterns"); + } else { + throw exception; + } + } + public static boolean isMatch(final String pattern, final String input) { final Pattern pat = Pattern.compile(pattern); final Matcher mat = pat.matcher(input);