diff --git a/app/src/main/java/app/revanced/integrations/music/patches/components/ShareSheetMenuFilter.java b/app/src/main/java/app/revanced/integrations/music/patches/components/ShareSheetMenuFilter.java index fe61f0bf6a..2a1bc5d9da 100644 --- a/app/src/main/java/app/revanced/integrations/music/patches/components/ShareSheetMenuFilter.java +++ b/app/src/main/java/app/revanced/integrations/music/patches/components/ShareSheetMenuFilter.java @@ -2,10 +2,10 @@ import androidx.annotation.Nullable; -import app.revanced.integrations.shared.patches.components.Filter; -import app.revanced.integrations.shared.patches.components.StringFilterGroup; import app.revanced.integrations.music.patches.misc.ShareSheetPatch; import app.revanced.integrations.music.settings.Settings; +import app.revanced.integrations.shared.patches.components.Filter; +import app.revanced.integrations.shared.patches.components.StringFilterGroup; /** * Abuse LithoFilter for {@link ShareSheetPatch}. diff --git a/app/src/main/java/app/revanced/integrations/music/patches/video/PlaybackSpeedPatch.java b/app/src/main/java/app/revanced/integrations/music/patches/video/PlaybackSpeedPatch.java index 713ce24ad1..e816931699 100644 --- a/app/src/main/java/app/revanced/integrations/music/patches/video/PlaybackSpeedPatch.java +++ b/app/src/main/java/app/revanced/integrations/music/patches/video/PlaybackSpeedPatch.java @@ -23,6 +23,10 @@ public static void userSelectedPlaybackSpeed(final float playbackSpeed) { return; Settings.DEFAULT_PLAYBACK_SPEED.save(playbackSpeed); + + if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST.get()) + return; + showToastShort(str("revanced_remember_playback_speed_toast", playbackSpeed + "x")); } } diff --git a/app/src/main/java/app/revanced/integrations/music/patches/video/VideoQualityPatch.java b/app/src/main/java/app/revanced/integrations/music/patches/video/VideoQualityPatch.java index 93a1790407..f0a5f6fb9a 100644 --- a/app/src/main/java/app/revanced/integrations/music/patches/video/VideoQualityPatch.java +++ b/app/src/main/java/app/revanced/integrations/music/patches/video/VideoQualityPatch.java @@ -60,6 +60,9 @@ private static void userSelectedVideoQuality(final int defaultQuality) { default -> wifiQualitySetting.save(defaultQuality); } + if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) + return; + Utils.showToastShort(str("revanced_remember_video_quality_" + networkType.getName(), defaultQuality + "p")); } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/music/returnyoutubedislike/ReturnYouTubeDislike.java b/app/src/main/java/app/revanced/integrations/music/returnyoutubedislike/ReturnYouTubeDislike.java index a604cb5025..a0239b604f 100644 --- a/app/src/main/java/app/revanced/integrations/music/returnyoutubedislike/ReturnYouTubeDislike.java +++ b/app/src/main/java/app/revanced/integrations/music/returnyoutubedislike/ReturnYouTubeDislike.java @@ -151,8 +151,6 @@ public class ReturnYouTubeDislike { @NonNull private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, @NonNull RYDVoteData voteData) { - // Note: Some locales use right to left layout (Arabic, Hebrew, etc). - // If making changes to this code, change device settings to a RTL language and verify layout is correct. String oldLikesString = oldSpannable.toString(); // YouTube creators can hide the like count on a video, @@ -191,8 +189,8 @@ private static SpannableString createDislikeSpan(@NonNull Spanned oldSpannable, // middle separator String middleSeparatorString = compactLayout - ? " " + MIDDLE_SEPARATOR_CHARACTER + " " - : " \u2009" + MIDDLE_SEPARATOR_CHARACTER + "\u2009 "; // u2009 = 'narrow space' character + ? "\u200E " + MIDDLE_SEPARATOR_CHARACTER + " " + : "\u200E \u2009" + MIDDLE_SEPARATOR_CHARACTER + "\u2009 "; // u2009 = 'narrow space' character final int shapeInsertionIndex = middleSeparatorString.length() / 2; Spannable middleSeparatorSpan = new SpannableString(middleSeparatorString); ShapeDrawable shapeDrawable = new ShapeDrawable(new OvalShape()); diff --git a/app/src/main/java/app/revanced/integrations/music/settings/Settings.java b/app/src/main/java/app/revanced/integrations/music/settings/Settings.java index e6b9180354..382cf0d60e 100644 --- a/app/src/main/java/app/revanced/integrations/music/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/music/settings/Settings.java @@ -145,7 +145,9 @@ public class Settings extends BaseSettings { // PreferenceScreen: Video public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", "0.5\n0.8\n1.0\n1.2\n1.5\n1.8\n2.0", true); public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", TRUE); + public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE); public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", TRUE); + public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_video_quality_last_selected_toast", TRUE); public static final FloatSetting DEFAULT_PLAYBACK_SPEED = new FloatSetting("revanced_default_playback_speed", 1.0f); public static final IntegerSetting DEFAULT_VIDEO_QUALITY_MOBILE = new IntegerSetting("revanced_default_video_quality_mobile", -2); public static final IntegerSetting DEFAULT_VIDEO_QUALITY_WIFI = new IntegerSetting("revanced_default_video_quality_wifi", -2); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/alternativethumbnails/AlternativeThumbnailsPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/alternativethumbnails/AlternativeThumbnailsPatch.java index 772b978134..1686169cbe 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/alternativethumbnails/AlternativeThumbnailsPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/alternativethumbnails/AlternativeThumbnailsPatch.java @@ -188,10 +188,10 @@ private static ThumbnailOption optionSettingForCurrentNavigation() { // Unknown tab, treat as the home tab; return homeOption; } - if (selectedNavButton == NavigationButton.HOME) { + if (selectedNavButton.isHomeTab()) { return homeOption; } - if (selectedNavButton == NavigationButton.SUBSCRIPTIONS || selectedNavButton == NavigationButton.NOTIFICATIONS) { + if (selectedNavButton.isSubscriptionsTab() || selectedNavButton.isNotificationTab()) { return subscriptionsOption; } // A library tab variant is active. diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/FeedComponentsFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/FeedComponentsFilter.java index 561f16e284..9c3f75eec7 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/FeedComponentsFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/FeedComponentsFilter.java @@ -18,6 +18,13 @@ public final class FeedComponentsFilter extends Filter { "horizontalCollectionSwipeProtector=null"; private static final String CONVERSATION_CONTEXT_SUBSCRIPTIONS_IDENTIFIER = "heightConstraint=null"; + + private static final ByteArrayFilterGroup expansion = + new ByteArrayFilterGroup( + null, + "inline_expansion" + ); + private static final ByteArrayFilterGroup mixPlaylists = new ByteArrayFilterGroup( Settings.HIDE_MIX_PLAYLISTS, @@ -35,6 +42,7 @@ public final class FeedComponentsFilter extends Filter { private final StringFilterGroup channelProfile; private final StringFilterGroup communityPosts; private final StringFilterGroup libraryShelf; + private final StringFilterGroup newExpansion; private final ByteArrayFilterGroup visitStoreButton; private static final StringTrieSearch communityPostsFeedGroupSearch = new StringTrieSearch(); @@ -70,6 +78,7 @@ public FeedComponentsFilter() { null, "post_base_wrapper", "image_post_root", + "images_post_root", "text_post_root" ); @@ -104,6 +113,19 @@ public FeedComponentsFilter() { "page_header.eml" // new layout ); + // The path for the new type of 'Expandable chip under videos' is as follows: + // + // CellType|CellType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|ContainerType|CellType|CellType|ContainerType|ContainerType|CollectionType|CellType|CellType|ContainerType| + // + // Unlike other litho elements, this one does not contain any words that can identify this layout. + // Since 'CellType' and 'ContainerType' are already used in several layouts, this filter alone has some limitations in identifying the layout. + // + // Related issues: https://github.com/inotia00/ReVanced_Extended/issues/2173 + newExpansion = new StringFilterGroup( + Settings.HIDE_EXPANDABLE_CHIP, + "CellType|CellType|ContainerType|ContainerType|ContainerType|" + ); + visitStoreButton = new ByteArrayFilterGroup( null, "header_store_button" @@ -187,6 +209,7 @@ public FeedComponentsFilter() { imageShelf, latestPosts, movieShelf, + newExpansion, notifyMe, playables, subscriptionsChannelBar, @@ -264,6 +287,11 @@ public boolean isFiltered(String path, @Nullable String identifier, String allVa return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); } return false; + } else if (matchedGroup == newExpansion) { + if (contentIndex == 0 && expansion.check(protobufBufferArray).isFiltered()) { + return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); + } + return false; } else if (matchedGroup == channelProfile) { if (contentIndex == 0 && visitStoreButton.check(protobufBufferArray).isFiltered()) { return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java index 10a7cfebb9..48dad65c09 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/KeywordContentFilter.java @@ -1,5 +1,13 @@ package app.revanced.integrations.youtube.patches.components; +import static java.lang.Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS; +import static java.lang.Character.UnicodeBlock.HIRAGANA; +import static java.lang.Character.UnicodeBlock.KATAKANA; +import static java.lang.Character.UnicodeBlock.KHMER; +import static java.lang.Character.UnicodeBlock.LAO; +import static java.lang.Character.UnicodeBlock.MYANMAR; +import static java.lang.Character.UnicodeBlock.THAI; +import static java.lang.Character.UnicodeBlock.TIBETAN; import static app.revanced.integrations.shared.utils.StringRef.str; import static app.revanced.integrations.youtube.shared.NavigationBar.NavigationButton; @@ -7,10 +15,9 @@ import androidx.annotation.Nullable; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import app.revanced.integrations.shared.patches.components.Filter; import app.revanced.integrations.shared.patches.components.StringFilterGroup; @@ -24,7 +31,7 @@ /** *
- * Allows hiding home feed and search results based on keywords and/or channel names.
+ * Allows hiding home feed and search results based on video title keywords and/or channel names.
  *
  * Limitations:
  * - Searching for a keyword phrase will give no search results.
@@ -39,13 +46,13 @@
  *   (ie: "mr beast" automatically filters "Mr Beast" and "MR BEAST").
  * - Keywords present in the layout or video data cannot be used as filters, otherwise all videos
  *   will always be hidden.  This patch checks for some words of these words.
+ * - When using whole word syntax, some keywords may need additional pluralized variations.
  */
 @SuppressWarnings("unused")
 public final class KeywordContentFilter extends Filter {
 
     /**
-     * Strings found in the buffer for every video.
-     * Full strings should be specified, as they are compared using {@link String#contains(CharSequence)}.
+     * Strings found in the buffer for every videos.  Full strings should be specified.
      * 

* This list does not include every common buffer string, and this can be added/changed as needed. * Words must be entered with the exact casing as found in the buffer. @@ -80,7 +87,7 @@ public final class KeywordContentFilter extends Filter { "search_vwc_description_transition_key", "g-high-recZ", // Text and litho components found in the buffer that belong to path filters. - "metadata.eml", + "expandable_metadata.eml", "thumbnail.eml", "avatar.eml", "overflow_button.eml", @@ -99,7 +106,8 @@ public final class KeywordContentFilter extends Filter { "search_video_with_context.eml", "video_with_context.eml", // Subscription tab videos. "related_video_with_context.eml", - "video_lockup_with_attachment.eml", // A/B tests. + // A/B test for subscribed video, and sometimes when tablet layout is enabled. + "video_lockup_with_attachment.eml", "compact_video.eml", "inline_shorts", "shorts_video_cell", @@ -131,6 +139,12 @@ public final class KeywordContentFilter extends Filter { "overflow_button.eml" ); + /** + * Minimum keyword/phrase length to prevent excessively broad content filtering. + * Only applies when not using whole word syntax. + */ + private static final int MINIMUM_KEYWORD_LENGTH = 3; + /** * Threshold for {@link #filteredVideosPercentage} * that indicates all or nearly all videos have been filtered. @@ -142,6 +156,8 @@ public final class KeywordContentFilter extends Filter { private static final long ALL_VIDEOS_FILTERED_BACKOFF_MILLISECONDS = 60 * 1000; // 60 seconds + private static final int UTF8_MAX_BYTE_COUNT = 4; + /** * Rolling average of how many videos were filtered by a keyword. * Used to detect if a keyword passes the initial check against {@link #STRINGS_IN_EVERY_BUFFER} @@ -175,7 +191,6 @@ public final class KeywordContentFilter extends Filter { private volatile String lastKeywordPhrasesParsed; private volatile ByteTrieSearch bufferSearch; - private volatile List regexPatterns; private static void logNavigationState(String state) { // Enable locally to debug filtering. Default off to reduce log spam. @@ -194,7 +209,7 @@ private static String titleCaseFirstWordOnly(String sentence) { return sentence; } final int firstCodePoint = sentence.codePointAt(0); - // In some non-English languages title case is different from uppercase. + // In some non English languages title case is different than uppercase. return new StringBuilder() .appendCodePoint(Character.toTitleCase(firstCodePoint)) .append(sentence, Character.charCount(firstCodePoint), sentence.length()) @@ -226,19 +241,171 @@ private static String capitalizeAllFirstLetters(String sentence) { } /** - * @return If the phrase will hide all videos. Not an exhaustive check. + * @return If the string contains any characters from languages that do not use spaces between words. */ - private static boolean phrasesWillHideAllVideos(@NonNull String[] phrases) { - for (String commonString : STRINGS_IN_EVERY_BUFFER) { - if (Utils.containsAny(commonString, phrases)) { + private static boolean isLanguageWithNoSpaces(String text) { + for (int i = 0, length = text.length(); i < length; ) { + final int codePoint = text.codePointAt(i); + + Character.UnicodeBlock block = Character.UnicodeBlock.of(codePoint); + if (block == CJK_UNIFIED_IDEOGRAPHS // Chinese and Kanji + || block == HIRAGANA // Japanese Hiragana + || block == KATAKANA // Japanese Katakana + || block == THAI + || block == LAO + || block == MYANMAR + || block == KHMER + || block == TIBETAN) { return true; } + + i += Character.charCount(codePoint); + } + + return false; + } + + + /** + * @return If the phrase will hide all videos. Not an exhaustive check. + */ + private static boolean phrasesWillHideAllVideos(@NonNull String[] phrases, boolean matchWholeWords) { + for (String phrase : phrases) { + for (String commonString : STRINGS_IN_EVERY_BUFFER) { + if (matchWholeWords) { + byte[] commonStringBytes = commonString.getBytes(StandardCharsets.UTF_8); + int matchIndex = 0; + while (true) { + matchIndex = commonString.indexOf(phrase, matchIndex); + if (matchIndex < 0) break; + + if (keywordMatchIsWholeWord(commonStringBytes, matchIndex, phrase.length())) { + return true; + } + + matchIndex++; + } + } else if (Utils.containsAny(commonString, phrases)) { + return true; + } + } } + return false; } - private synchronized void parseKeywords() { // Must be synchronized since Litho is multithreaded. + /** + * @return If the start and end indexes are not surrounded by other letters. + * If the indexes are surrounded by numbers/symbols/punctuation it is considered a whole word. + */ + private static boolean keywordMatchIsWholeWord(byte[] text, int keywordStartIndex, int keywordLength) { + final Integer codePointBefore = getUtf8CodePointBefore(text, keywordStartIndex); + if (codePointBefore != null && Character.isLetter(codePointBefore)) { + return false; + } + + final Integer codePointAfter = getUtf8CodePointAt(text, keywordStartIndex + keywordLength); + //noinspection RedundantIfStatement + if (codePointAfter != null && Character.isLetter(codePointAfter)) { + return false; + } + + return true; + } + + /** + * @return The UTF8 character point immediately before the index, + * or null if the bytes before the index is not a valid UTF8 character. + */ + @Nullable + private static Integer getUtf8CodePointBefore(byte[] data, int index) { + int characterByteCount = 0; + while (--index >= 0 && ++characterByteCount <= UTF8_MAX_BYTE_COUNT) { + if (isValidUtf8(data, index, characterByteCount)) { + return decodeUtf8ToCodePoint(data, index, characterByteCount); + } + } + + return null; + } + + /** + * @return The UTF8 character point at the index, + * or null if the index holds no valid UTF8 character. + */ + @Nullable + private static Integer getUtf8CodePointAt(byte[] data, int index) { + int characterByteCount = 0; + final int dataLength = data.length; + while (index + characterByteCount < dataLength && ++characterByteCount <= UTF8_MAX_BYTE_COUNT) { + if (isValidUtf8(data, index, characterByteCount)) { + return decodeUtf8ToCodePoint(data, index, characterByteCount); + } + } + + return null; + } + + public static boolean isValidUtf8(byte[] data, int startIndex, int numberOfBytes) { + switch (numberOfBytes) { + case 1 -> { // 0xxxxxxx (ASCII) + return (data[startIndex] & 0x80) == 0; + } + case 2 -> { // 110xxxxx, 10xxxxxx + return (data[startIndex] & 0xE0) == 0xC0 + && (data[startIndex + 1] & 0xC0) == 0x80; + } + case 3 -> { // 1110xxxx, 10xxxxxx, 10xxxxxx + return (data[startIndex] & 0xF0) == 0xE0 + && (data[startIndex + 1] & 0xC0) == 0x80 + && (data[startIndex + 2] & 0xC0) == 0x80; + } + case 4 -> { // 11110xxx, 10xxxxxx, 10xxxxxx, 10xxxxxx + return (data[startIndex] & 0xF8) == 0xF0 + && (data[startIndex + 1] & 0xC0) == 0x80 + && (data[startIndex + 2] & 0xC0) == 0x80 + && (data[startIndex + 3] & 0xC0) == 0x80; + } + } + + throw new IllegalArgumentException("numberOfBytes: " + numberOfBytes); + } + + public static int decodeUtf8ToCodePoint(byte[] data, int startIndex, int numberOfBytes) { + switch (numberOfBytes) { + case 1 -> { + return data[startIndex]; + } + case 2 -> { + return ((data[startIndex] & 0x1F) << 6) | + (data[startIndex + 1] & 0x3F); + } + case 3 -> { + return ((data[startIndex] & 0x0F) << 12) | + ((data[startIndex + 1] & 0x3F) << 6) | + (data[startIndex + 2] & 0x3F); + } + case 4 -> { + return ((data[startIndex] & 0x07) << 18) | + ((data[startIndex + 1] & 0x3F) << 12) | + ((data[startIndex + 2] & 0x3F) << 6) | + (data[startIndex + 3] & 0x3F); + } + } + throw new IllegalArgumentException("numberOfBytes: " + numberOfBytes); + } + + private static boolean phraseUsesWholeWordSyntax(String phrase) { + return phrase.startsWith("\"") && phrase.endsWith("\""); + } + + private static String stripWholeWordSyntax(String phrase) { + return phrase.substring(1, phrase.length() - 1); + } + + private synchronized void parseKeywords() { // Must be synchronized since Litho is multi-threaded. String rawKeywords = Settings.HIDE_KEYWORD_CONTENT_PHRASES.get(); + //noinspection StringEquality if (rawKeywords == lastKeywordPhrasesParsed) { Logger.printDebug(() -> "Using previously initialized search"); @@ -247,28 +414,44 @@ private synchronized void parseKeywords() { // Must be synchronized since Litho ByteTrieSearch search = new ByteTrieSearch(); String[] split = rawKeywords.split("\n"); - - List patterns = new ArrayList<>(); - if (split.length != 0) { // Linked Set so log statement are more organized and easier to read. - Set keywords = new LinkedHashSet<>(10 * split.length); + // Map is: Phrase -> isWholeWord + Map keywords = new LinkedHashMap<>(10 * split.length); for (String phrase : split) { - // Remove any trailing white space the user may have accidentally included. + // Remove any trailing spaces the user may have accidentally included. phrase = phrase.stripTrailing(); if (phrase.isBlank()) continue; - // Add common casing that might appear. + final boolean wholeWordMatching; + if (phraseUsesWholeWordSyntax(phrase)) { + if (phrase.length() == 2) { + continue; // Empty "" phrase + } + phrase = stripWholeWordSyntax(phrase); + wholeWordMatching = true; + } else if (phrase.length() < MINIMUM_KEYWORD_LENGTH && !isLanguageWithNoSpaces(phrase)) { + // Allow phrases of 1 and 2 characters if using a + // language that does not use spaces between words. + + // Do not reset the setting. Keep the invalid keywords so the user can fix the mistake. + Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_length", phrase, MINIMUM_KEYWORD_LENGTH)); + continue; + } else { + wholeWordMatching = false; + } + + // Common casing that might appear. // - // This could be simplified by adding case-insensitive search to the prefix search, + // This could be simplified by adding case insensitive search to the prefix search, // which is very simple to add to StringTreSearch for Unicode and ByteTrieSearch for ASCII. // // But to support Unicode with ByteTrieSearch would require major changes because // UTF-8 characters can be different byte lengths, which does // not allow comparing two different byte arrays using simple plain array indexes. // - // Instead, add all common case variations of the words. + // Instead use all common case variations of the words. String[] phraseVariations = { phrase, phrase.toLowerCase(), @@ -276,22 +459,43 @@ private synchronized void parseKeywords() { // Must be synchronized since Litho capitalizeAllFirstLetters(phrase), phrase.toUpperCase() }; - if (phrasesWillHideAllVideos(phraseVariations)) { - Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_common", phrase)); + if (phrasesWillHideAllVideos(phraseVariations, wholeWordMatching)) { + String toastMessage; + // If whole word matching is off, but would pass with on, then show a different toast. + if (!wholeWordMatching && !phrasesWillHideAllVideos(phraseVariations, true)) { + toastMessage = "revanced_hide_keyword_toast_invalid_common_whole_word_required"; + } else { + toastMessage = "revanced_hide_keyword_toast_invalid_common"; + } + + Utils.showToastLong(str(toastMessage, phrase)); continue; } - keywords.addAll(Arrays.asList(phraseVariations)); + for (String variation : phraseVariations) { + // Check if the same phrase is declared both with and without quotes. + Boolean existing = keywords.get(variation); + if (existing == null) { + keywords.put(variation, wholeWordMatching); + } else if (existing != wholeWordMatching) { + Utils.showToastLong(str("revanced_hide_keyword_toast_invalid_conflicting", phrase)); + break; + } + } } - for (String keyword : keywords) { - String regex = "\\b(" + Pattern.quote(keyword) + ")\\b"; - patterns.add(Pattern.compile(regex)); - // Use a callback to get the keyword that matched. - // TrieSearch could have this built in, but that's slightly more complicated since - // the strings are stored as a byte array and embedded in the search tree. + for (Map.Entry entry : keywords.entrySet()) { + String keyword = entry.getKey(); + //noinspection ExtractMethodRecommender + final boolean isWholeWord = entry.getValue(); TrieSearch.TriePatternMatchedCallback callback = - (textSearched, matchedStartIndex, matchedLength, callbackParameter) -> { + (textSearched, startIndex, matchLength, callbackParameter) -> { + if (isWholeWord && !keywordMatchIsWholeWord(textSearched, startIndex, matchLength)) { + return false; + } + + Logger.printDebug(() -> (isWholeWord ? "Matched whole keyword: '" + : "Matched keyword: '") + keyword + "'"); // noinspection unchecked ((MutableReference) callbackParameter).value = keyword; return true; @@ -300,11 +504,10 @@ private synchronized void parseKeywords() { // Must be synchronized since Litho search.addPattern(stringBytes, callback); } - Logger.printDebug(() -> "Search using: (" + search.getEstimatedMemorySize() + " KB) keywords: " + keywords); + Logger.printDebug(() -> "Search using: (" + search.getEstimatedMemorySize() + " KB) keywords: " + keywords.keySet()); } bufferSearch = search; - regexPatterns = patterns; timeToResumeFiltering = 0; filteredVideosPercentage = 0; lastKeywordPhrasesParsed = rawKeywords; // Must set last. @@ -357,10 +560,10 @@ private boolean hideKeywordSettingIsActive() { if (selectedNavButton == null) { return hideHome; // Unknown tab, treat the same as home. } - if (selectedNavButton == NavigationButton.HOME) { + if (selectedNavButton.isHomeTab()) { return hideHome; } - if (selectedNavButton == NavigationButton.SUBSCRIPTIONS) { + if (selectedNavButton.isSubscriptionsTab()) { return hideSubscriptions; } // User is in the Library or Notifications tab. @@ -402,7 +605,7 @@ public boolean isFiltered(String path, @Nullable String identifier, String allVa // Field is intentionally compared using reference equality. //noinspection StringEquality if (Settings.HIDE_KEYWORD_CONTENT_PHRASES.get() != lastKeywordPhrasesParsed) { - // User changed the keywords. + // User changed the keywords or whole word setting. parseKeywords(); } @@ -414,21 +617,10 @@ public boolean isFiltered(String path, @Nullable String identifier, String allVa return false; // Do not update statistics. } - if (Settings.HIDE_KEYWORD_CONTENT_FULL_WORD.get()) { - String content = new String(protobufBufferArray, StandardCharsets.UTF_8); - for (Pattern pattern : regexPatterns) { - Matcher matcher = pattern.matcher(content); - if (matcher.find()) { - updateStats(true, matcher.group(1)); - return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); - } - } - } else { - MutableReference matchRef = new MutableReference<>(); - if (bufferSearch.matches(protobufBufferArray, matchRef)) { - updateStats(true, matchRef.value); - return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); - } + MutableReference matchRef = new MutableReference<>(); + if (bufferSearch.matches(protobufBufferArray, matchRef)) { + updateStats(true, matchRef.value); + return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); } updateStats(false, null); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/PlayerFlyoutMenuFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/PlayerFlyoutMenuFilter.java index c7f76d1615..6f2bcaad37 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/PlayerFlyoutMenuFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/PlayerFlyoutMenuFilter.java @@ -61,10 +61,6 @@ public PlayerFlyoutMenuFilter() { ); flyoutFilterGroupList.addAll( - new ByteArrayFilterGroup( - Settings.HIDE_PLAYER_FLYOUT_MENU_AMBIENT, - "yt_outline_screen_light" - ), new ByteArrayFilterGroup( Settings.HIDE_PLAYER_FLYOUT_MENU_AUDIO_TRACK, "yt_outline_person_radar" @@ -114,6 +110,10 @@ public PlayerFlyoutMenuFilter() { Settings.HIDE_PLAYER_FLYOUT_MENU_STABLE_VOLUME, "volume_stable" ), + new ByteArrayFilterGroup( + Settings.HIDE_PLAYER_FLYOUT_MENU_SLEEP_TIMER, + "yt_outline_moon_z_" + ), new ByteArrayFilterGroup( Settings.HIDE_PLAYER_FLYOUT_MENU_STATS_FOR_NERDS, "yt_outline_statistics_graph" diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsButtonFilter.java b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsButtonFilter.java index c1ca090fd4..07ae96a699 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsButtonFilter.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/components/ShortsButtonFilter.java @@ -29,8 +29,9 @@ public final class ShortsButtonFilter extends Filter { private final StringFilterGroup subscribeButton; private final StringFilterGroup joinButton; - private final StringFilterGroup paidPromotionButton; private final StringFilterGroup pausedOverlayButtons; + private final StringFilterGroup metaPanelButton; + private final ByteArrayFilterGroupList pausedOverlayButtonsGroupList = new ByteArrayFilterGroupList(); private final ByteArrayFilterGroup shortsCommentDisabled; @@ -40,12 +41,19 @@ public final class ShortsButtonFilter extends Filter { private final StringFilterGroup actionBar; private final ByteArrayFilterGroupList videoActionButtonGroupList = new ByteArrayFilterGroupList(); - private final ByteArrayFilterGroup shopButton = new ByteArrayFilterGroup( - Settings.HIDE_SHORTS_SHOP_BUTTON, - "yt_outline_bag_" + private final ByteArrayFilterGroup useThisSoundButton = new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_USE_THIS_SOUND_BUTTON, + "yt_outline_camera" ); public ShortsButtonFilter() { + StringFilterGroup floatingButton = new StringFilterGroup( + Settings.HIDE_SHORTS_FLOATING_BUTTON, + "floating_action_button" + ); + + addIdentifierCallbacks(floatingButton); + pausedOverlayButtons = new StringFilterGroup( null, "shorts_paused_state" @@ -81,6 +89,16 @@ public ShortsButtonFilter() { "immersive_live_header" ); + StringFilterGroup paidPromotionButton = new StringFilterGroup( + Settings.HIDE_SHORTS_PAID_PROMOTION_LABEL, + "reel_player_disclosure.eml" + ); + + metaPanelButton = new StringFilterGroup( + null, + "|ContainerType|button.eml|" + ); + joinButton = new StringFilterGroup( Settings.HIDE_SHORTS_JOIN_BUTTON, "sponsor_button" @@ -91,11 +109,6 @@ public ShortsButtonFilter() { "subscribe_button" ); - paidPromotionButton = new StringFilterGroup( - Settings.HIDE_SHORTS_PAID_PROMOTION_LABEL, - "reel_player_disclosure.eml" - ); - actionBar = new StringFilterGroup( null, "shorts_action_bar" @@ -107,7 +120,7 @@ public ShortsButtonFilter() { ); addPathCallbacks( - suggestedAction, actionBar, joinButton, subscribeButton, + suggestedAction, actionBar, joinButton, subscribeButton, metaPanelButton, paidPromotionButton, pausedOverlayButtons, channelBar, fullVideoLinkLabel, videoTitle, reelSoundMetadata, infoPanel, liveHeader ); @@ -148,11 +161,24 @@ public ShortsButtonFilter() { ) ); + // + // Paused overlay buttons. + // + pausedOverlayButtonsGroupList.addAll( + new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_TRENDS_BUTTON, + "yt_outline_fire_" + ), + new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_SHOPPING_BUTTON, + "yt_outline_bag_" + ) + ); + // // Suggested actions. // suggestedActionsGroupList.addAll( - shopButton, new ByteArrayFilterGroup( Settings.HIDE_SHORTS_TAGGED_PRODUCTS, // Product buttons show pictures of the products, and does not have any unique icons to identify. @@ -160,7 +186,11 @@ public ShortsButtonFilter() { "PAproduct_listZ" ), new ByteArrayFilterGroup( - Settings.HIDE_SHORTS_LOCATION_LABEL, + Settings.HIDE_SHORTS_SHOP_BUTTON, + "yt_outline_bag_" + ), + new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_LOCATION_BUTTON, "yt_outline_location_point_" ), new ByteArrayFilterGroup( @@ -168,20 +198,25 @@ public ShortsButtonFilter() { "yt_outline_list_add_" ), new ByteArrayFilterGroup( - Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS, + Settings.HIDE_SHORTS_SEARCH_SUGGESTIONS_BUTTON, "yt_outline_search_" ), new ByteArrayFilterGroup( Settings.HIDE_SHORTS_SUPER_THANKS_BUTTON, "yt_outline_dollar_sign_heart_" - ) + ), + new ByteArrayFilterGroup( + Settings.HIDE_SHORTS_USE_TEMPLATE_BUTTON, + "yt_outline_template_add" + ), + useThisSoundButton ); } @Override public boolean isFiltered(String path, @Nullable String identifier, String allValue, byte[] protobufBufferArray, StringFilterGroup matchedGroup, FilterContentType contentType, int contentIndex) { - if (matchedGroup == subscribeButton || matchedGroup == joinButton || matchedGroup == paidPromotionButton) { + if (matchedGroup == subscribeButton || matchedGroup == joinButton) { // Selectively filter to avoid false positive filtering of other subscribe/join buttons. if (StringUtils.startsWithAny(path, REEL_CHANNEL_BAR_PATH, REEL_LIVE_HEADER_PATH, REEL_METAPANEL_PATH)) { return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); @@ -189,6 +224,13 @@ public boolean isFiltered(String path, @Nullable String identifier, String allVa return false; } + if (matchedGroup == metaPanelButton) { + if (path.startsWith(REEL_METAPANEL_PATH) && useThisSoundButton.check(protobufBufferArray).isFiltered()) { + return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); + } + return false; + } + // Video action buttons (like, dislike, comment, share, remix) have the same path. if (matchedGroup == actionBar) { // If the Comment button is hidden, there is no need to check {@code REEL_COMMENTS_DISABLED_PATTERN}. @@ -216,7 +258,7 @@ public boolean isFiltered(String path, @Nullable String identifier, String allVa if (Settings.HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS.get()) { return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); } else if (StringUtils.contains(path, SHORTS_PAUSED_STATE_BUTTON_PATH)) { - if (shopButton.check(protobufBufferArray).isFiltered()) { + if (pausedOverlayButtonsGroupList.check(protobufBufferArray).isFiltered()) { return super.isFiltered(path, identifier, allValue, protobufBufferArray, matchedGroup, contentType, contentIndex); } } diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/general/GeneralPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/general/GeneralPatch.java index 7408f62c92..ec11bbfcf1 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/general/GeneralPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/general/GeneralPatch.java @@ -256,10 +256,15 @@ public static boolean hideSnackBar() { private static final Map shouldHideMap = new EnumMap<>(NavigationButton.class) { { put(NavigationButton.HOME, Settings.HIDE_NAVIGATION_HOME_BUTTON.get()); + put(NavigationButton.HOME_CAIRO, Settings.HIDE_NAVIGATION_HOME_BUTTON.get()); put(NavigationButton.SHORTS, Settings.HIDE_NAVIGATION_SHORTS_BUTTON.get()); + put(NavigationButton.SHORTS_CAIRO, Settings.HIDE_NAVIGATION_SHORTS_BUTTON.get()); put(NavigationButton.SUBSCRIPTIONS, Settings.HIDE_NAVIGATION_SUBSCRIPTIONS_BUTTON.get()); + put(NavigationButton.SUBSCRIPTIONS_CAIRO, Settings.HIDE_NAVIGATION_SUBSCRIPTIONS_BUTTON.get()); put(NavigationButton.CREATE, Settings.HIDE_NAVIGATION_CREATE_BUTTON.get()); + put(NavigationButton.CREATE_CAIRO, Settings.HIDE_NAVIGATION_CREATE_BUTTON.get()); put(NavigationButton.NOTIFICATIONS, Settings.HIDE_NAVIGATION_NOTIFICATIONS_BUTTON.get()); + put(NavigationButton.NOTIFICATIONS_CAIRO, Settings.HIDE_NAVIGATION_NOTIFICATIONS_BUTTON.get()); put(NavigationButton.LIBRARY_LOGGED_OUT, Settings.HIDE_NAVIGATION_LIBRARY_BUTTON.get()); put(NavigationButton.LIBRARY_INCOGNITO, Settings.HIDE_NAVIGATION_LIBRARY_BUTTON.get()); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/LiveStreamRenderer.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/LiveStreamRenderer.java deleted file mode 100644 index 5ff6098bc1..0000000000 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/LiveStreamRenderer.java +++ /dev/null @@ -1,31 +0,0 @@ -package app.revanced.integrations.youtube.patches.misc; - -import org.jetbrains.annotations.NotNull; - -/** - * @noinspection ALL - */ -public final class LiveStreamRenderer { - public final String videoId; - public final String client; - public final boolean playabilityOk; - public final boolean isLiveStream; - - public LiveStreamRenderer(String videoId, String client, boolean playabilityOk, boolean isLiveStream) { - this.videoId = videoId; - this.client = client; - this.playabilityOk = playabilityOk; - this.isLiveStream = isLiveStream; - } - - @NotNull - @Override - public String toString() { - return "LiveStreamRenderer{" + - "videoId=" + videoId + - ", client=" + client + - ", playabilityOk=" + playabilityOk + - ", isLiveStream=" + isLiveStream + - '}'; - } -} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofClientPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofClientPatch.java deleted file mode 100644 index 862d1a6e5e..0000000000 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofClientPatch.java +++ /dev/null @@ -1,523 +0,0 @@ -package app.revanced.integrations.youtube.patches.misc; - -import static app.revanced.integrations.shared.utils.Utils.submitOnBackgroundThread; - -import android.net.Uri; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.apache.commons.lang3.StringUtils; - -import java.util.List; -import java.util.Objects; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import app.revanced.integrations.shared.utils.Logger; -import app.revanced.integrations.youtube.patches.misc.requests.LiveStreamRendererRequester; -import app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.ClientType; -import app.revanced.integrations.youtube.patches.misc.requests.StoryboardRendererRequester; -import app.revanced.integrations.youtube.settings.Settings; - -/** - * @noinspection ALL - */ -public class SpoofClientPatch { - private static final boolean SPOOF_CLIENT_ENABLED = Settings.SPOOF_CLIENT.get(); - - /** - * Clips or Shorts Parameters. - */ - private static final String[] CLIPS_OR_SHORTS_PARAMETERS = { - "kAIB", // Clips - "8AEB" // Shorts - }; - - /** - * iOS client is used for Clips or Shorts. - */ - private static volatile boolean isShortsOrClips; - - /** - * Any unreachable ip address. Used to intentionally fail requests. - */ - private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0"; - private static final Uri UNREACHABLE_HOST_URI = Uri.parse(UNREACHABLE_HOST_URI_STRING); - - /** - * Last spoofed client type. - */ - private static volatile ClientType lastSpoofedClientType; - - /** - * Last video id loaded. Used to prevent reloading the same spec multiple times. - */ - @NonNull - private static volatile String lastPlayerResponseVideoId = ""; - - @Nullable - private static volatile Future liveStreamRendererFuture; - - @Nullable - private static volatile Future storyboardRendererFuture; - - /** - * Injection point. - * Blocks /get_watch requests by returning a localhost URI. - * - * @param playerRequestUri The URI of the player request. - * @return Localhost URI if the request is a /get_watch request, otherwise the original URI. - */ - public static Uri blockGetWatchRequest(Uri playerRequestUri) { - if (SPOOF_CLIENT_ENABLED) { - try { - String path = playerRequestUri.getPath(); - - if (path != null && path.contains("get_watch")) { - Logger.printDebug(() -> "Blocking: " + playerRequestUri + " by returning: " + UNREACHABLE_HOST_URI_STRING); - - return UNREACHABLE_HOST_URI; - } - } catch (Exception ex) { - Logger.printException(() -> "blockGetWatchRequest failure", ex); - } - } - - return playerRequestUri; - } - - /** - * Injection point. - *

- * Blocks /initplayback requests. - */ - public static String blockInitPlaybackRequest(String originalUrlString) { - if (SPOOF_CLIENT_ENABLED) { - try { - Uri originalUri = Uri.parse(originalUrlString); - String path = originalUri.getPath(); - - if (path != null && path.contains("initplayback")) { - String replacementUriString = (getSpoofClientType() != ClientType.ANDROID_TESTSUITE) - ? UNREACHABLE_HOST_URI_STRING - // TODO: Ideally, a local proxy could be setup and block - // the request the same way as Burp Suite is capable of - // because that way the request is never sent to YouTube unnecessarily. - // Just using localhost unfortunately does not work. - : originalUri.buildUpon().clearQuery().build().toString(); - - Logger.printDebug(() -> "Blocking: " + originalUrlString + " by returning: " + replacementUriString); - - return replacementUriString; - } - } catch (Exception ex) { - Logger.printException(() -> "blockInitPlaybackRequest failure", ex); - } - } - - return originalUrlString; - } - - private static ClientType getSpoofClientType() { - if (isShortsOrClips) { - lastSpoofedClientType = Settings.SPOOF_CLIENT_SHORTS.get(); - return lastSpoofedClientType; - } - LiveStreamRenderer renderer = getLiveStreamRenderer(false); - if (renderer != null) { - if (renderer.isLiveStream) { - lastSpoofedClientType = Settings.SPOOF_CLIENT_LIVESTREAM.get(); - return lastSpoofedClientType; - } - if (!renderer.playabilityOk) { - lastSpoofedClientType = Settings.SPOOF_CLIENT_FALLBACK.get(); - return lastSpoofedClientType; - } - } - lastSpoofedClientType = Settings.SPOOF_CLIENT_GENERAL.get(); - return lastSpoofedClientType; - } - - /** - * Injection point. - */ - public static int getClientTypeId(int originalClientTypeId) { - if (SPOOF_CLIENT_ENABLED) { - return getSpoofClientType().id; - } - - return originalClientTypeId; - } - - /** - * Injection point. - */ - public static String getClientVersion(String originalClientVersion) { - if (SPOOF_CLIENT_ENABLED) { - return getSpoofClientType().version; - } - - return originalClientVersion; - } - - /** - * Injection point. - */ - public static String getClientModel(String originalClientModel) { - if (SPOOF_CLIENT_ENABLED) { - return getSpoofClientType().model; - } - - return originalClientModel; - } - - /** - * Injection point. - */ - public static String getOsVersion(String originalOsVersion) { - if (SPOOF_CLIENT_ENABLED) { - return getSpoofClientType().osVersion; - } - - return originalOsVersion; - } - - /** - * Injection point. - */ - public static String getUserAgent(String originalUserAgent) { - if (SPOOF_CLIENT_ENABLED) { - ClientType clientType = getSpoofClientType(); - if (clientType == ClientType.IOS) { - Logger.printDebug(() -> "Replaced: '" + originalUserAgent + "' with: '" - + clientType.userAgent + "'"); - return clientType.userAgent; - } - } - - return originalUserAgent; - } - - /** - * Injection point. - */ - public static boolean isClientSpoofingEnabled() { - return SPOOF_CLIENT_ENABLED; - } - - /** - * Injection point. - */ - public static boolean enablePlayerGesture(boolean original) { - return SPOOF_CLIENT_ENABLED || original; - } - - /** - * Injection point. - * When spoofing the client to iOS or Android Testsuite the playback speed menu is missing from the player response. - * Return true to force create the playback speed menu. - */ - public static boolean forceCreatePlaybackSpeedMenu(boolean original) { - if (SPOOF_CLIENT_ENABLED) { - return true; - } - - return original; - } - - /** - * Injection point. - * When spoofing the client to iOS, background audio only playback of livestreams fails. - * Return true to force enable audio background play. - */ - public static boolean overrideBackgroundAudioPlayback() { - return SPOOF_CLIENT_ENABLED && - BackgroundPlaybackPatch.playbackIsNotShort(); - } - - /** - * Injection point. - * When spoofing the client to Android TV the playback speed menu is missing from the player response. - * Return false to force create the playback speed menu. - */ - public static boolean forceCreatePlaybackSpeedMenuReversed(boolean original) { - if (SPOOF_CLIENT_ENABLED) { - return false; - } - - return original; - } - - private static final Uri VIDEO_STATS_PLAYBACK_URI = Uri.parse("https://www.youtube.com/api/stats/playback?ns=yt&ver=2&final=1"); - - private static final String PARAM_DOC_ID = "docid"; - private static final String PARAM_LEN = "len"; - private static final String PARAM_CPN = "cpn"; - - private static final String PARAM_EVENT_ID = "ei"; - private static final String PARAM_VM = "vm"; - private static final String PARAM_OF = "of"; - - private static String mDocId; - private static String mLen; - private static String mCpn; - private static String mEventId; - private static String mVisitorMonitoringData; - private static String mOfParam; - - /** - * Injection point. - */ - public static void setCpn(String cpn) { - if (SPOOF_CLIENT_ENABLED && !Objects.equals(mCpn, cpn)) { - mCpn = cpn; - } - } - - /** - * Injection point. - * - * Parse parameters from the Tracking URL. - * See yuliskov/MediaServiceCore. - */ - public static void setTrackingUriParameter(Uri trackingUri) { - try { - if (SPOOF_CLIENT_ENABLED) { - String path = trackingUri.getPath(); - - if (path == null || (!path.contains("playback") && !path.contains("watchtime"))) { - return; - } - - mDocId = getQueryParameter(trackingUri, PARAM_DOC_ID); - mLen = getQueryParameter(trackingUri, PARAM_LEN); - mEventId = getQueryParameter(trackingUri, PARAM_EVENT_ID); - mVisitorMonitoringData = getQueryParameter(trackingUri, PARAM_VM); - mOfParam = getQueryParameter(trackingUri, PARAM_OF); - - Logger.printDebug(() -> "docId: " + mDocId + ", len: " + mLen + ", eventId: " + mEventId + ", visitorMonitoringData: " + mVisitorMonitoringData + ", of: " + mOfParam); - } - } catch (Exception ex) { - Logger.printException(() -> "setTrackingUriParameter failure", ex); - } - } - - /** - * Injection point. - * This only works on YouTube 18.38.45 or earlier. - * - * Build a Tracking URL. - * This does not include the last watched time. - * See yuliskov/MediaServiceCore. - */ - public static Uri overrideTrackingUrl(Uri trackingUrl) { - try { - if (SPOOF_CLIENT_ENABLED && - getSpoofClientType() == ClientType.IOS && - trackingUrl.toString().contains("youtube.com/csi") && - !StringUtils.isAnyEmpty(mDocId, mLen, mCpn, mEventId, mVisitorMonitoringData, mOfParam) - ) { - final Uri videoStatsPlaybackUri = VIDEO_STATS_PLAYBACK_URI - .buildUpon() - .appendQueryParameter(PARAM_DOC_ID, mDocId) - .appendQueryParameter(PARAM_LEN, mLen) - .appendQueryParameter(PARAM_CPN, mCpn) - .appendQueryParameter(PARAM_EVENT_ID, mEventId) - .appendQueryParameter(PARAM_VM, mVisitorMonitoringData) - .appendQueryParameter(PARAM_OF, mOfParam) - .build(); - - Logger.printDebug(() -> "Replaced: '" + trackingUrl + "' with: '" + videoStatsPlaybackUri + "'"); - return videoStatsPlaybackUri; - } - } catch (Exception ex) { - Logger.printException(() -> "overrideTrackingUrl failure", ex); - } - - return trackingUrl; - } - - private static String getQueryParameter(Uri uri, String key) { - List queryParams = uri.getQueryParameters(key); - if (queryParams == null || queryParams.isEmpty()) { - return ""; - } else { - return queryParams.get(0); - } - } - - /** - * Injection point. - */ - public static String appendSpoofedClient(String videoFormat) { - try { - if (SPOOF_CLIENT_ENABLED && Settings.SPOOF_CLIENT_STATS_FOR_NERDS.get() - && !TextUtils.isEmpty(videoFormat)) { - // Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages - return "\u202D" + videoFormat + String.format("\u2009(%s)", lastSpoofedClientType.friendlyName); // u202D = left to right override - } - } catch (Exception ex) { - Logger.printException(() -> "appendSpoofedClient failure", ex); - } - - return videoFormat; - } - - /** - * Injection point. - */ - public static String setPlayerResponseVideoId(@NonNull String videoId, @Nullable String parameters, boolean isShortAndOpeningOrPlaying) { - if (SPOOF_CLIENT_ENABLED) { - isShortsOrClips = playerParameterIsClipsOrShorts(parameters, isShortAndOpeningOrPlaying); - - if (!isShortsOrClips) { - fetchPlayerResponseRenderer(videoId, Settings.SPOOF_CLIENT_GENERAL.get()); - } - } - - return parameters; // Return the original value since we are observing and not modifying. - } - - /** - * @return If the player parameters are for a Short or Clips. - */ - private static boolean playerParameterIsClipsOrShorts(@Nullable String playerParameter, boolean isShortAndOpeningOrPlaying) { - if (isShortAndOpeningOrPlaying) { - return true; - } - - return playerParameter != null && StringUtils.startsWithAny(playerParameter, CLIPS_OR_SHORTS_PARAMETERS); - } - - private static void fetchPlayerResponseRenderer(@NonNull String videoId, @NonNull ClientType clientType) { - if (!videoId.equals(lastPlayerResponseVideoId)) { - lastPlayerResponseVideoId = videoId; - liveStreamRendererFuture = submitOnBackgroundThread(() -> LiveStreamRendererRequester.getLiveStreamRenderer(videoId, clientType)); - storyboardRendererFuture = submitOnBackgroundThread(() -> StoryboardRendererRequester.getStoryboardRenderer(videoId)); - } - // Block until the renderer fetch completes. - // This is desired because if this returns without finishing the fetch - // then video will start playback but the storyboard is not ready yet. - getLiveStreamRenderer(true); - } - - @Nullable - private static LiveStreamRenderer getLiveStreamRenderer(boolean waitForCompletion) { - Future future = liveStreamRendererFuture; - if (future != null) { - try { - if (waitForCompletion || future.isDone()) { - return future.get(20000, TimeUnit.MILLISECONDS); // Any arbitrarily large timeout. - } // else, return null. - } catch (TimeoutException ex) { - Logger.printDebug(() -> "Could not get renderer (get timed out)"); - } catch (ExecutionException | InterruptedException ex) { - // Should never happen. - Logger.printException(() -> "Could not get renderer", ex); - } - } - return null; - } - - @Nullable - private static StoryboardRenderer getStoryboardRenderer(boolean waitForCompletion) { - Future future = storyboardRendererFuture; - if (future != null) { - try { - if (waitForCompletion || future.isDone()) { - return future.get(20000, TimeUnit.MILLISECONDS); // Any arbitrarily large timeout. - } // else, return null. - } catch (TimeoutException ex) { - Logger.printDebug(() -> "Could not get renderer (get timed out)"); - } catch (ExecutionException | InterruptedException ex) { - // Should never happen. - Logger.printException(() -> "Could not get renderer", ex); - } - } - return null; - } - - private static boolean useFetchedStoryboardRenderer() { - if (!SPOOF_CLIENT_ENABLED || isShortsOrClips) { - return false; - } - - // No seekbar thumbnail or low quality seekbar thumbnail. - final ClientType clientType = getSpoofClientType(); - return clientType == ClientType.ANDROID_TESTSUITE || clientType == ClientType.ANDROID_UNPLUGGED; - } - - private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec, - boolean returnNullIfLiveStream) { - if (useFetchedStoryboardRenderer()) { - final StoryboardRenderer renderer = getStoryboardRenderer(false); - if (renderer != null) { - if (returnNullIfLiveStream && renderer.isLiveStream) { - return null; - } - String spec = renderer.spec; - if (spec != null) { - return spec; - } - } - } - - return originalStoryboardRendererSpec; - } - - /** - * Injection point. - * Called from background threads and from the main thread. - */ - @Nullable - public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) { - return getStoryboardRendererSpec(originalStoryboardRendererSpec, false); - } - - /** - * Injection point. - * Uses additional check to handle live streams. - * Called from background threads and from the main thread. - */ - @Nullable - public static String getStoryboardDecoderRendererSpec(String originalStoryboardRendererSpec) { - return getStoryboardRendererSpec(originalStoryboardRendererSpec, true); - } - - /** - * Injection point. - */ - public static int getStoryboardRecommendedLevel(int originalLevel) { - if (useFetchedStoryboardRenderer()) { - final StoryboardRenderer renderer = getStoryboardRenderer(false); - if (renderer != null) { - Integer recommendedLevel = renderer.recommendedLevel; - if (recommendedLevel != null) return recommendedLevel; - } - } - - return originalLevel; - } - - /** - * Injection point. Forces seekbar to be shown for paid videos or - * if {@link Settings#SPOOF_PLAYER_PARAMETER} is not enabled. - */ - public static boolean getSeekbarThumbnailOverrideValue() { - if (!useFetchedStoryboardRenderer()) { - return false; - } - final StoryboardRenderer renderer = getStoryboardRenderer(false); - if (renderer == null) { - // Spoof storyboard renderer is turned off, - // video is paid, or the storyboard fetch timed out. - // Show empty thumbnails so the seek time and chapters still show up. - return true; - } - return renderer.spec != null; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofPlayerParameterPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofPlayerParameterPatch.java deleted file mode 100644 index 153ac4efb1..0000000000 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofPlayerParameterPatch.java +++ /dev/null @@ -1,220 +0,0 @@ -package app.revanced.integrations.youtube.patches.misc; - -import static app.revanced.integrations.shared.utils.Utils.containsAny; -import static app.revanced.integrations.shared.utils.Utils.submitOnBackgroundThread; -import static app.revanced.integrations.youtube.patches.misc.requests.StoryboardRendererRequester.getStoryboardRenderer; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import app.revanced.integrations.shared.utils.Logger; -import app.revanced.integrations.youtube.settings.Settings; -import app.revanced.integrations.youtube.shared.PlayerType; -import app.revanced.integrations.youtube.shared.VideoInformation; - -/** - * @noinspection ALL - *

- * Even if user spoof any player parameters with the client name "ANDROID", if a valid DroidGuard result is not sent, - * user always receive a response with video id 'aQvGIIdgFDM' (the following content is not available on this app). - * YouTube.js#623 - * Therefore, this patch is no longer valid. - *

- * Currently, the only client name available on Android without DroidGuard results is "ANDROID_TESTSUITE". - * invidious#4650 - */ -@Deprecated -public class SpoofPlayerParameterPatch { - private static final boolean spoofParameter = Settings.SPOOF_PLAYER_PARAMETER.get(); - private static final boolean spoofParameterInFeed = Settings.SPOOF_PLAYER_PARAMETER_IN_FEED.get(); - - /** - * Parameter (also used by - * YouTube.js) - * to fix playback issues. - */ - private static final String INCOGNITO_PARAMETERS = "CgIIAQ%3D%3D"; - - /** - * Parameters used when playing clips. - */ - private static final String CLIPS_PARAMETERS = "kAIB"; - - /** - * Parameters causing playback issues. - */ - private static final String[] AUTOPLAY_PARAMETERS = { - "YAHI", // Autoplay in feed. - "SAFg" // Autoplay in scrim. - }; - - /** - * Parameter used for autoplay in scrim. - * Prepend this parameter to mute video playback (for autoplay in feed). - */ - private static final String SCRIM_PARAMETER = "SAFgAXgB"; - - /** - * Last video id loaded. Used to prevent reloading the same spec multiple times. - */ - @Nullable - private static volatile String lastPlayerResponseVideoId; - - @Nullable - private static volatile Future rendererFuture; - - private static volatile boolean useOriginalStoryboardRenderer; - - @Nullable - private static StoryboardRenderer getRenderer(boolean waitForCompletion) { - Future future = rendererFuture; - if (future != null) { - try { - if (waitForCompletion || future.isDone()) { - return future.get(20000, TimeUnit.MILLISECONDS); // Any arbitrarily large timeout. - } // else, return null. - } catch (TimeoutException ex) { - Logger.printDebug(() -> "Could not get renderer (get timed out)"); - } catch (ExecutionException | InterruptedException ex) { - // Should never happen. - Logger.printException(() -> "Could not get renderer", ex); - } - } - return null; - } - - /** - * Injection point. - *

- * {@link VideoInformation#getVideoId()} cannot be used because it is injected after PlayerResponse. - * Therefore, we use the videoId called from PlaybackStartDescriptor. - * - * @param videoId Original video id value. - * @param parameters Original player parameter value. - */ - public static String spoofParameter(@NonNull String videoId, @Nullable String parameters, boolean isShortAndOpeningOrPlaying) { - try { - Logger.printDebug(() -> "Original player parameter value: " + parameters); - - if (!spoofParameter) { - return parameters; - } - - // Shorts do not need to be spoofed. - if (useOriginalStoryboardRenderer = VideoInformation.playerParametersAreShort(parameters)) { - return parameters; - } - - // Clip's player parameters contain important information such as where the video starts, where it ends, and whether it loops. - // Clips are 60 seconds or less in length, so no spoofing. - if (useOriginalStoryboardRenderer = parameters.length() > 150 || parameters.startsWith(CLIPS_PARAMETERS)) { - return parameters; - } - - final boolean isPlayingFeed = PlayerType.getCurrent() == PlayerType.INLINE_MINIMAL - && containsAny(parameters, AUTOPLAY_PARAMETERS); - if (isPlayingFeed) { - //noinspection AssignmentUsedAsCondition - if (useOriginalStoryboardRenderer = !spoofParameterInFeed) { - // Don't spoof the feed video playback. This will cause video playback issues, - // but only if user continues watching for more than 1 minute. - return parameters; - } - // Spoof the feed video. Video will show up in watch history and video subtitles are missing. - fetchStoryboardRenderer(videoId); - return SCRIM_PARAMETER + INCOGNITO_PARAMETERS; - } - - fetchStoryboardRenderer(videoId); - } catch (Exception ex) { - Logger.printException(() -> "spoofParameter failure", ex); - } - return INCOGNITO_PARAMETERS; - } - - private static void fetchStoryboardRenderer(@NonNull String videoId) { - if (!videoId.equals(lastPlayerResponseVideoId)) { - rendererFuture = submitOnBackgroundThread(() -> getStoryboardRenderer(videoId)); - lastPlayerResponseVideoId = videoId; - } - // Block until the renderer fetch completes. - // This is desired because if this returns without finishing the fetch - // then video will start playback but the storyboard is not ready yet. - getRenderer(true); - } - - private static String getStoryboardRendererSpec(String originalStoryboardRendererSpec, - boolean returnNullIfLiveStream) { - if (spoofParameter && !useOriginalStoryboardRenderer) { - final StoryboardRenderer renderer = getRenderer(false); - if (renderer != null) { - if (returnNullIfLiveStream && renderer.isLiveStream) { - return null; - } - String spec = renderer.spec; - if (spec != null) { - return spec; - } - } - } - - return originalStoryboardRendererSpec; - } - - /** - * Injection point. - * Called from background threads and from the main thread. - */ - @Nullable - public static String getStoryboardRendererSpec(String originalStoryboardRendererSpec) { - return getStoryboardRendererSpec(originalStoryboardRendererSpec, false); - } - - /** - * Injection point. - * Uses additional check to handle live streams. - * Called from background threads and from the main thread. - */ - @Nullable - public static String getStoryboardDecoderRendererSpec(String originalStoryboardRendererSpec) { - return getStoryboardRendererSpec(originalStoryboardRendererSpec, true); - } - - /** - * Injection point. - */ - public static int getStoryboardRecommendedLevel(int originalLevel) { - if (spoofParameter && !useOriginalStoryboardRenderer) { - final StoryboardRenderer renderer = getRenderer(false); - if (renderer != null) { - Integer recommendedLevel = renderer.recommendedLevel; - if (recommendedLevel != null) return recommendedLevel; - } - } - - return originalLevel; - } - - /** - * Injection point. Forces seekbar to be shown for paid videos or - * if {@link Settings#SPOOF_PLAYER_PARAMETER} is not enabled. - */ - public static boolean getSeekbarThumbnailOverrideValue() { - if (!spoofParameter) { - return false; - } - final StoryboardRenderer renderer = getRenderer(false); - if (renderer == null) { - // Spoof storyboard renderer is turned off, - // video is paid, or the storyboard fetch timed out. - // Show empty thumbnails so the seek time and chapters still show up. - return true; - } - return renderer.spec != null; - } -} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofStreamingDataPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofStreamingDataPatch.java new file mode 100644 index 0000000000..168558a84c --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/SpoofStreamingDataPatch.java @@ -0,0 +1,177 @@ +package app.revanced.integrations.youtube.patches.misc; + +import android.net.Uri; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.nio.ByteBuffer; +import java.util.Map; + +import app.revanced.integrations.shared.settings.Setting; +import app.revanced.integrations.shared.utils.Logger; +import app.revanced.integrations.shared.utils.Utils; +import app.revanced.integrations.youtube.patches.misc.client.AppClient.ClientType; +import app.revanced.integrations.youtube.patches.misc.requests.StreamingDataRequest; +import app.revanced.integrations.youtube.settings.Settings; +import app.revanced.integrations.youtube.shared.VideoInformation; + +@SuppressWarnings("unused") +public class SpoofStreamingDataPatch { + private static final boolean SPOOF_STREAMING_DATA = Settings.SPOOF_STREAMING_DATA.get(); + + /** + * Any unreachable ip address. Used to intentionally fail requests. + */ + private static final String UNREACHABLE_HOST_URI_STRING = "https://127.0.0.0"; + + private static volatile Map fetchHeaders; + + /** + * Injection point. + *

+ * Blocks /initplayback requests. + */ + public static String blockInitPlaybackRequest(String originalUrlString) { + if (SPOOF_STREAMING_DATA) { + try { + var originalUri = Uri.parse(originalUrlString); + String path = originalUri.getPath(); + + if (path != null && path.contains("initplayback")) { + Logger.printDebug(() -> "Blocking 'initplayback' by returning unreachable url"); + + return UNREACHABLE_HOST_URI_STRING; + } + } catch (Exception ex) { + Logger.printException(() -> "blockInitPlaybackRequest failure", ex); + } + } + + return originalUrlString; + } + + /** + * Injection point. + */ + public static boolean isSpoofingEnabled() { + return SPOOF_STREAMING_DATA; + } + + /** + * Injection point. + */ + public static void setFetchHeaders(String url, Map headers) { + if (SPOOF_STREAMING_DATA) { + try { + Uri uri = Uri.parse(url); + String path = uri.getPath(); + if (path != null && path.contains("browse")) { + fetchHeaders = headers; + } + } catch (Exception ex) { + Logger.printException(() -> "setFetchHeaders failure", ex); + } + } + } + + /** + * Injection point. + */ + public static void fetchStreamingData(@NonNull String videoId, boolean isShortAndOpeningOrPlaying) { + if (SPOOF_STREAMING_DATA) { + try { + final boolean videoIdIsShort = VideoInformation.lastPlayerResponseIsShort(); + // Shorts shelf in home and subscription feed causes player response hook to be called, + // and the 'is opening/playing' parameter will be false. + // This hook will be called again when the Short is actually opened. + if (videoIdIsShort && !isShortAndOpeningOrPlaying) { + return; + } + + StreamingDataRequest.fetchRequestIfNeeded(videoId, fetchHeaders); + } catch (Exception ex) { + Logger.printException(() -> "fetchStreamingData failure", ex); + } + } + } + + /** + * Injection point. + * Fix playback by replace the streaming data. + * Called after {@link #setFetchHeaders(String, Map)} . + */ + @Nullable + public static ByteBuffer getStreamingData(String videoId) { + if (SPOOF_STREAMING_DATA) { + try { + Utils.verifyOffMainThread(); + + StreamingDataRequest request = StreamingDataRequest.getRequestForVideoId(videoId); + if (request != null) { + var stream = request.getStream(); + if (stream != null) { + Logger.printDebug(() -> "Overriding video stream: " + videoId); + return stream; + } + } + + Logger.printDebug(() -> "Not overriding streaming data (video stream is null): " + videoId); + } catch (Exception ex) { + Logger.printException(() -> "getStreamingData failure", ex); + } + } + + return null; + } + + /** + * Injection point. + * Called after {@link #getStreamingData(String)}. + */ + @Nullable + public static byte[] removeVideoPlaybackPostBody(Uri uri, int method, byte[] postData) { + if (SPOOF_STREAMING_DATA) { + try { + final int methodPost = 2; + if (method == methodPost) { + String path = uri.getPath(); + String clientName = "c"; + final boolean iosClient = ClientType.IOS.name().equals(uri.getQueryParameter(clientName)); + if (iosClient && path != null && path.contains("videoplayback")) { + return null; + } + } + } catch (Exception ex) { + Logger.printException(() -> "removeVideoPlaybackPostBody failure", ex); + } + } + + return postData; + } + + /** + * Injection point. + */ + public static String appendSpoofedClient(String videoFormat) { + try { + if (SPOOF_STREAMING_DATA && Settings.SPOOF_STREAMING_DATA_STATS_FOR_NERDS.get() + && !TextUtils.isEmpty(videoFormat)) { + // Force LTR layout, to match the same LTR video time/length layout YouTube uses for all languages + return "\u202D" + videoFormat + String.format("\u2009(%s)", StreamingDataRequest.getLastSpoofedClientName()); // u202D = left to right override + } + } catch (Exception ex) { + Logger.printException(() -> "appendSpoofedClient failure", ex); + } + + return videoFormat; + } + + public static final class ForceiOSAVCAvailability implements Setting.Availability { + @Override + public boolean isAvailable() { + return Settings.SPOOF_STREAMING_DATA.get() && Settings.SPOOF_STREAMING_DATA_TYPE.get() == ClientType.IOS; + } + } +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/StoryboardRenderer.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/StoryboardRenderer.java deleted file mode 100644 index e5b626312c..0000000000 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/StoryboardRenderer.java +++ /dev/null @@ -1,38 +0,0 @@ -package app.revanced.integrations.youtube.patches.misc; - -import androidx.annotation.Nullable; - -import org.jetbrains.annotations.NotNull; - -/** - * @noinspection ALL - */ -public final class StoryboardRenderer { - public final String videoId; - @Nullable - public final String spec; - public final boolean isLiveStream; - /** - * Recommended image quality level, or NULL if no recommendation exists. - */ - @Nullable - public final Integer recommendedLevel; - - public StoryboardRenderer(String videoId, @Nullable String spec, boolean isLiveStream, @Nullable Integer recommendedLevel) { - this.videoId = videoId; - this.spec = spec; - this.isLiveStream = isLiveStream; - this.recommendedLevel = recommendedLevel; - } - - @NotNull - @Override - public String toString() { - return "StoryboardRenderer{" + - "videoId=" + videoId + - ", isLiveStream=" + isLiveStream + - ", spec='" + spec + '\'' + - ", recommendedLevel=" + recommendedLevel + - '}'; - } -} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/client/AppClient.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/client/AppClient.java new file mode 100644 index 0000000000..f19fd8ef9b --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/client/AppClient.java @@ -0,0 +1,279 @@ +package app.revanced.integrations.youtube.patches.misc.client; + +import static app.revanced.integrations.shared.utils.StringRef.str; + +import android.os.Build; + +import androidx.annotation.Nullable; + +import app.revanced.integrations.shared.utils.PackageUtils; + +public class AppClient { + + // WEB + private static final String CLIENT_VERSION_WEB = "2.20240726.00.00"; + private static final String DEVICE_MODEL_WEB = "Surface Book 3"; + private static final String OS_VERSION_WEB = "10"; + private static final String USER_AGENT_WEB = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:129.0)" + + " Gecko/20100101" + + " Firefox/129.0"; + + // ANDROID + private static final String CLIENT_VERSION_ANDROID = PackageUtils.getVersionName(); + private static final String DEVICE_MODEL_ANDROID = Build.MODEL; + private static final String OS_NAME_ANDROID = "Android"; + private static final String OS_VERSION_ANDROID = Build.VERSION.RELEASE; + private static final int ANDROID_SDK_VERSION_ANDROID = Build.VERSION.SDK_INT; + private static final String USER_AGENT_ANDROID = "com.google.android.youtube/" + + CLIENT_VERSION_ANDROID + + " (Linux; U; Android " + + OS_VERSION_ANDROID + + "; GB) gzip"; + + // IOS + /** + * The hardcoded client version of the iOS app used for InnerTube requests with this client. + * + *

+ * It can be extracted by getting the latest release version of the app on + * the App + * Store page of the YouTube app, in the {@code What’s New} section. + *

+ */ + private static final String CLIENT_VERSION_IOS = "19.16.3"; + private static final String DEVICE_MAKE_IOS = "Apple"; + /** + * The device machine id for the iPhone XS Max (iPhone11,4), used to get 60fps. + * The device machine id for the iPhone 15 Pro Max (iPhone16,2), used to get HDR with AV1 hardware decoding. + * + *

+ * See this GitHub Gist for more + * information. + *

+ */ + private static final String DEVICE_MODEL_IOS = DeviceHardwareSupport.allowAV1() + ? "iPhone16,2" + : "iPhone11,4"; + private static final String OS_NAME_IOS = "iOS"; + /** + * The minimum supported OS version for the iOS YouTube client is iOS 14.0. + * Using an invalid OS version will use the AVC codec. + */ + private static final String OS_VERSION_IOS = DeviceHardwareSupport.allowVP9() + ? "17.6.1.21G101" + : "13.7.17H35"; + private static final String USER_AGENT_VERSION_IOS = DeviceHardwareSupport.allowVP9() + ? "17_6_1" + : "13_7"; + private static final String USER_AGENT_IOS = "com.google.ios.youtube/" + + CLIENT_VERSION_IOS + + "(" + + DEVICE_MODEL_IOS + + "; U; CPU iOS " + + USER_AGENT_VERSION_IOS + + " like Mac OS X)"; + + // ANDROID VR + /** + * The hardcoded client version of the Android VR app used for InnerTube requests with this client. + * + *

+ * It can be extracted by getting the latest release version of the app on + * the App + * Store page of the YouTube app, in the {@code Additional details} section. + *

+ */ + private static final String CLIENT_VERSION_ANDROID_VR = "1.56.21"; + /** + * The device machine id for the Meta Quest 3, used to get opus codec with the Android VR client. + * + *

+ * See this GitLab for more + * information. + *

+ */ + private static final String DEVICE_MODEL_ANDROID_VR = "Quest 3"; + private static final String OS_VERSION_ANDROID_VR = "12"; + /** + * The SDK version for Android 12 is 31, + * but for some reason the build.props for the {@code Quest 3} state that the SDK version is 32. + */ + private static final int ANDROID_SDK_VERSION_ANDROID_VR = 32; + /** + * Package name for YouTube VR (Google DayDream): com.google.android.apps.youtube.vr (Deprecated) + * Package name for YouTube VR (Meta Quests): com.google.android.apps.youtube.vr.oculus + * Package name for YouTube VR (ByteDance Pico 4): com.google.android.apps.youtube.vr.pico + */ + private static final String USER_AGENT_ANDROID_VR = "com.google.android.youtube/" + + CLIENT_VERSION_ANDROID_VR + + " (Linux; U; Android " + + OS_VERSION_ANDROID_VR + + "; GB) gzip"; + + // ANDROID UNPLUGGED + private static final String CLIENT_VERSION_ANDROID_UNPLUGGED = "8.16.0"; + /** + * The device machine id for the Chromecast with Google TV 4K. + * + *

+ * See this GitLab for more + * information. + *

+ */ + private static final String DEVICE_MODEL_ANDROID_UNPLUGGED = "Chromecast"; + private static final String OS_VERSION_ANDROID_UNPLUGGED = "12"; + private static final int ANDROID_SDK_VERSION_ANDROID_UNPLUGGED = 31; + private static final String USER_AGENT_ANDROID_UNPLUGGED = "com.google.android.apps.youtube.unplugged/" + + CLIENT_VERSION_ANDROID_UNPLUGGED + + " (Linux; U; Android " + + OS_VERSION_ANDROID_UNPLUGGED + + "; GB) gzip"; + + // ANDROID TESTSUITE + private static final String CLIENT_VERSION_ANDROID_TESTSUITE = "1.9"; + private static final String USER_AGENT_ANDROID_TESTSUITE = "com.google.android.youtube/" + + CLIENT_VERSION_ANDROID_TESTSUITE + + " (Linux; U; Android " + + OS_VERSION_ANDROID + + "; GB) gzip"; + + // TVHTML5 SIMPLY EMBEDDED PLAYER + private static final String CLIENT_VERSION_TVHTML5_SIMPLY_EMBEDDED_PLAYER = "2.0"; + + private AppClient() { + } + + public enum ClientType { + WEB(1, + null, + DEVICE_MODEL_WEB, + CLIENT_VERSION_WEB, + null, + OS_VERSION_WEB, + null, + USER_AGENT_WEB + ), + ANDROID(3, + null, + DEVICE_MODEL_ANDROID, + CLIENT_VERSION_ANDROID, + OS_NAME_ANDROID, + OS_VERSION_ANDROID, + ANDROID_SDK_VERSION_ANDROID, + USER_AGENT_ANDROID + ), + IOS(5, + DEVICE_MAKE_IOS, + DEVICE_MODEL_IOS, + CLIENT_VERSION_IOS, + OS_NAME_IOS, + OS_VERSION_IOS, + null, + USER_AGENT_IOS + ), + ANDROID_VR(28, + null, + DEVICE_MODEL_ANDROID_VR, + CLIENT_VERSION_ANDROID_VR, + OS_NAME_ANDROID, + OS_VERSION_ANDROID_VR, + ANDROID_SDK_VERSION_ANDROID_VR, + USER_AGENT_ANDROID_VR + ), + ANDROID_UNPLUGGED(29, + null, + DEVICE_MODEL_ANDROID_UNPLUGGED, + CLIENT_VERSION_ANDROID_UNPLUGGED, + OS_NAME_ANDROID, + OS_VERSION_ANDROID_UNPLUGGED, + ANDROID_SDK_VERSION_ANDROID_UNPLUGGED, + USER_AGENT_ANDROID_UNPLUGGED + ), + ANDROID_TESTSUITE(30, + null, + DEVICE_MODEL_ANDROID, + CLIENT_VERSION_ANDROID_TESTSUITE, + OS_NAME_ANDROID, + OS_VERSION_ANDROID, + ANDROID_SDK_VERSION_ANDROID, + USER_AGENT_ANDROID_TESTSUITE + ), + ANDROID_EMBEDDED_PLAYER(55, + null, + DEVICE_MODEL_ANDROID, + CLIENT_VERSION_ANDROID, + OS_NAME_ANDROID, + OS_VERSION_ANDROID, + ANDROID_SDK_VERSION_ANDROID, + USER_AGENT_ANDROID + ), + TVHTML5_SIMPLY_EMBEDDED_PLAYER(85, + null, + DEVICE_MODEL_WEB, + CLIENT_VERSION_TVHTML5_SIMPLY_EMBEDDED_PLAYER, + null, + OS_VERSION_WEB, + null, + USER_AGENT_WEB + ); + + public final String friendlyName; + + /** + * YouTube + * client type + */ + public final int id; + + /** + * Device manufacturer. + */ + @Nullable + public final String make; + + /** + * Device model, equivalent to {@link Build#MODEL} (System property: ro.product.model) + */ + public final String model; + + /** + * Device OS name. + */ + @Nullable + public final String osName; + + /** + * Device OS version. + */ + public final String osVersion; + + /** + * Player user-agent. + */ + public final String userAgent; + + /** + * Android SDK version, equivalent to {@link Build.VERSION#SDK} (System property: ro.build.version.sdk) + * Field is null if not applicable. + */ + public final Integer androidSdkVersion; + + /** + * App version. + */ + public final String appVersion; + + ClientType(int id, @Nullable String make, String model, String appVersion, @Nullable String osName, + String osVersion, Integer androidSdkVersion, String userAgent) { + this.friendlyName = str("revanced_spoof_streaming_data_type_entry_" + name().toLowerCase()); + this.id = id; + this.make = make; + this.model = model; + this.appVersion = appVersion; + this.osName = osName; + this.osVersion = osVersion; + this.androidSdkVersion = androidSdkVersion; + this.userAgent = userAgent; + } + } +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/client/DeviceHardwareSupport.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/client/DeviceHardwareSupport.java new file mode 100644 index 0000000000..9677f82a3a --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/client/DeviceHardwareSupport.java @@ -0,0 +1,53 @@ +package app.revanced.integrations.youtube.patches.misc.client; + +import android.media.MediaCodecInfo; +import android.media.MediaCodecList; +import android.os.Build; + +import app.revanced.integrations.shared.utils.Logger; +import app.revanced.integrations.youtube.settings.Settings; + +public class DeviceHardwareSupport { + private static final boolean DEVICE_HAS_HARDWARE_DECODING_VP9; + private static final boolean DEVICE_HAS_HARDWARE_DECODING_AV1; + + static { + boolean vp9found = false; + boolean av1found = false; + MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS); + final boolean deviceIsAndroidTenOrLater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q; + + for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) { + final boolean isHardwareAccelerated = deviceIsAndroidTenOrLater + ? codecInfo.isHardwareAccelerated() + : !codecInfo.getName().startsWith("OMX.google"); // Software decoder. + if (isHardwareAccelerated && !codecInfo.isEncoder()) { + for (String type : codecInfo.getSupportedTypes()) { + if (type.equalsIgnoreCase("video/x-vnd.on2.vp9")) { + vp9found = true; + } else if (type.equalsIgnoreCase("video/av01")) { + av1found = true; + } + } + } + } + + DEVICE_HAS_HARDWARE_DECODING_VP9 = vp9found; + DEVICE_HAS_HARDWARE_DECODING_AV1 = av1found; + + Logger.printDebug(() -> DEVICE_HAS_HARDWARE_DECODING_AV1 + ? "Device supports AV1 hardware decoding\n" + : "Device does not support AV1 hardware decoding\n" + + (DEVICE_HAS_HARDWARE_DECODING_VP9 + ? "Device supports VP9 hardware decoding" + : "Device does not support VP9 hardware decoding")); + } + + public static boolean allowVP9() { + return DEVICE_HAS_HARDWARE_DECODING_VP9 && !Settings.SPOOF_STREAMING_DATA_IOS_FORCE_AVC.get(); + } + + public static boolean allowAV1() { + return allowVP9() && DEVICE_HAS_HARDWARE_DECODING_AV1; + } +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/LiveStreamRendererRequester.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/LiveStreamRendererRequester.java deleted file mode 100644 index eba2749fdf..0000000000 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/LiveStreamRendererRequester.java +++ /dev/null @@ -1,158 +0,0 @@ -package app.revanced.integrations.youtube.patches.misc.requests; - -import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.GET_LIVE_STREAM_RENDERER; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; -import java.util.Objects; - -import app.revanced.integrations.shared.requests.Requester; -import app.revanced.integrations.shared.utils.Logger; -import app.revanced.integrations.shared.utils.Utils; -import app.revanced.integrations.youtube.patches.misc.LiveStreamRenderer; -import app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.ClientType; - -public class LiveStreamRendererRequester { - - private LiveStreamRendererRequester() { - } - - private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) { - Logger.printInfo(() -> toastMessage, ex); - } - - @Nullable - private static JSONObject fetchPlayerResponse(@NonNull String requestBody, - @NonNull String userAgent) { - final long startTime = System.currentTimeMillis(); - try { - Utils.verifyOffMainThread(); - Objects.requireNonNull(requestBody); - - final byte[] innerTubeBody = requestBody.getBytes(StandardCharsets.UTF_8); - - HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_LIVE_STREAM_RENDERER, userAgent); - connection.getOutputStream().write(innerTubeBody, 0, innerTubeBody.length); - - final int responseCode = connection.getResponseCode(); - if (responseCode == 200) return Requester.parseJSONObject(connection); - - // Always show a toast for this, as a non 200 response means something is broken. - handleConnectionError("Fetch livestreams not available: " + responseCode, null); - connection.disconnect(); - } catch (SocketTimeoutException ex) { - handleConnectionError("Fetch livestreams temporarily not available (API timed out)", ex); - } catch (IOException ex) { - handleConnectionError("Fetch livestreams temporarily not available: " + ex.getMessage(), ex); - } catch (Exception ex) { - Logger.printException(() -> "Fetch livestreams failed", ex); // Should never happen. - } finally { - Logger.printDebug(() -> "Request took: " + (System.currentTimeMillis() - startTime) + "ms"); - } - - return null; - } - - private static boolean isPlayabilityStatusOk(@NonNull JSONObject playerResponse) { - try { - return playerResponse.getJSONObject("playabilityStatus").getString("status").equals("OK"); - } catch (JSONException e) { - Logger.printDebug(() -> "Failed to get playabilityStatus for response: " + playerResponse); - } - - return false; - } - - private static boolean isLive(@NonNull JSONObject playerResponse) { - try { - return playerResponse.getJSONObject("videoDetails").getBoolean("isLive"); - } catch (JSONException e) { - Logger.printDebug(() -> "Failed to get videoDetails for response: " + playerResponse); - } - - return false; - } - - private static boolean isLiveContent(@NonNull JSONObject playerResponse) { - try { - return playerResponse.getJSONObject("videoDetails").getBoolean("isLiveContent"); - } catch (JSONException e) { - Logger.printDebug(() -> "Failed to get videoDetails for response: " + playerResponse); - } - - return false; - } - - /** - * Fetches the liveStreamRenderer from the innerTubeBody. - * - * @return LiveStreamRenderer or null if playabilityStatus is not OK. - */ - @Nullable - private static LiveStreamRenderer getLiveStreamRendererUsingBody(@NonNull String videoId, - @NonNull ClientType clientType) { - final JSONObject playerResponse = fetchPlayerResponse( - String.format(clientType.innerTubeBody, videoId), - clientType.userAgent - ); - if (playerResponse != null) - return getLiveStreamRendererUsingResponse(videoId, playerResponse, clientType); - - return null; - } - - @Nullable - private static LiveStreamRenderer getLiveStreamRendererUsingResponse(@NonNull String videoId, - @NonNull JSONObject playerResponse, - @NonNull ClientType clientType) { - try { - Logger.printDebug(() -> "Parsing liveStreamRenderer from response: " + playerResponse); - - final String clientName = clientType.name(); - final boolean isPlayabilityOk = isPlayabilityStatusOk(playerResponse); - final boolean isLiveStream = isLive(playerResponse) || isLiveContent(playerResponse); - - LiveStreamRenderer renderer = new LiveStreamRenderer( - videoId, - clientName, - isPlayabilityOk, - isLiveStream - ); - Logger.printDebug(() -> "Fetched: " + renderer); - - return renderer; - } catch (Exception e) { - Logger.printException(() -> "Failed to get liveStreamRenderer", e); - } - - return null; - } - - @Nullable - public static LiveStreamRenderer getLiveStreamRenderer(@NonNull String videoId, @NonNull ClientType clientType) { - Objects.requireNonNull(videoId); - - LiveStreamRenderer renderer = getLiveStreamRendererUsingBody(videoId, clientType); - if (renderer == null) { - String finalClientName1 = clientType.name(); - Logger.printDebug(() -> videoId + " not available using " + finalClientName1 + " client"); - - clientType = ClientType.TVHTML5_SIMPLY_EMBEDDED_PLAYER; - renderer = getLiveStreamRendererUsingBody(videoId, clientType); - if (renderer == null) { - String finalClientName2 = clientType.name(); - Logger.printDebug(() -> videoId + " not available using " + finalClientName2 + " client"); - } - } - - return renderer; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/PlayerRoutes.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/PlayerRoutes.java index f6835f2e1b..2addccfb87 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/PlayerRoutes.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/PlayerRoutes.java @@ -1,11 +1,5 @@ package app.revanced.integrations.youtube.patches.misc.requests; -import static app.revanced.integrations.shared.utils.StringRef.str; - -import android.media.MediaCodecInfo; -import android.media.MediaCodecList; -import android.os.Build; - import org.json.JSONException; import org.json.JSONObject; @@ -15,373 +9,71 @@ import app.revanced.integrations.shared.requests.Requester; import app.revanced.integrations.shared.requests.Route; import app.revanced.integrations.shared.utils.Logger; -import app.revanced.integrations.shared.utils.PackageUtils; +import app.revanced.integrations.youtube.patches.misc.client.AppClient.ClientType; public final class PlayerRoutes { - public static final Route.CompiledRoute GET_STORYBOARD_SPEC_RENDERER = new Route( - Route.Method.POST, - "player" + - "?fields=storyboards.playerStoryboardSpecRenderer," + - "storyboards.playerLiveStoryboardSpecRenderer," + - "playabilityStatus.status," + - "playabilityStatus.errorScreen" - ).compile(); + /** + * The base URL of requests of non-web clients to the InnerTube internal API. + */ + private static final String YOUTUBEI_V1_GAPIS_URL = "https://youtubei.googleapis.com/youtubei/v1/"; - public static final Route.CompiledRoute GET_LIVE_STREAM_RENDERER = new Route( + static final Route.CompiledRoute GET_STREAMING_DATA = new Route( Route.Method.POST, "player" + - "?fields=playabilityStatus.status," + - "videoDetails.isLive," + - "videoDetails.isLiveContent" + "?fields=streamingData" + + "&alt=proto" ).compile(); - - private static final String ANDROID_CLIENT_VERSION = PackageUtils.getVersionName(); - private static final String ANDROID_DEVICE_MODEL = Build.MODEL; - private static final String ANDROID_OS_RELEASE_VERSION = Build.VERSION.RELEASE; - private static final int ANDROID_OS_SDK_VERSION = Build.VERSION.SDK_INT; - private static final String ANDROID_USER_AGENT = "com.google.android.youtube/" + - ANDROID_CLIENT_VERSION + - " (Linux; U; Android " + - ANDROID_OS_RELEASE_VERSION + - "; GB) gzip"; - - private static final String ANDROID_TESTSUITE_CLIENT_VERSION = "1.9"; - - - private static final String ANDROID_UNPLUGGED_CLIENT_VERSION = "8.31.0"; - /** - * The device machine id for the Chromecast with Google TV 4K. - * - *

- * See this GitLab for more - * information. - *

- */ - private static final String ANDROID_UNPLUGGED_DEVICE_MODEL = "Chromecast"; - private static final String ANDROID_UNPLUGGED_OS_RELEASE_VERSION = "12"; - private static final int ANDROID_UNPLUGGED_OS_SDK_VERSION = 31; - private static final String ANDROID_UNPLUGGED_USER_AGENT = "com.google.android.apps.youtube.unplugged/" + - ANDROID_UNPLUGGED_CLIENT_VERSION + - " (Linux; U; Android " + - ANDROID_UNPLUGGED_OS_RELEASE_VERSION + - "; GB) gzip"; - - - /** - * The hardcoded client version of the Android VR app used for InnerTube requests with this client. - * - *

- * It can be extracted by getting the latest release version of the app on - * the App - * Store page of the YouTube app, in the {@code Additional details} section. - *

- */ - private static final String ANDROID_VR_CLIENT_VERSION = "1.58.14"; - - /** - * The device machine id for the Meta Quest 3, used to get opus codec with the Android VR client. - * - *

- * See this GitLab for more - * information. - *

- */ - private static final String ANDROID_VR_DEVICE_MODEL = "Quest 3"; - - private static final String ANDROID_VR_OS_RELEASE_VERSION = "12"; - /** - * The SDK version for Android 12 is 31, - * but for some reason the build.props for the {@code Quest 3} state that the SDK version is 32. - */ - private static final int ANDROID_VR_OS_SDK_VERSION = 32; - - /** - * Package name for YouTube VR (Google DayDream): com.google.android.apps.youtube.vr (Deprecated) - * Package name for YouTube VR (Meta Quests): com.google.android.apps.youtube.vr.oculus - * Package name for YouTube VR (ByteDance Pico 4): com.google.android.apps.youtube.vr.pico - */ - private static final String ANDROID_VR_USER_AGENT = "com.google.android.apps.youtube.vr.oculus/" + - ANDROID_VR_CLIENT_VERSION + - " (Linux; U; Android 12; GB) gzip"; - - - /** - * The hardcoded client version of the iOS app used for InnerTube requests with this client. - * - *

- * It can be extracted by getting the latest release version of the app on - * the App - * Store page of the YouTube app, in the {@code What’s New} section. - *

- */ - private static final String IOS_CLIENT_VERSION = "19.30.2"; - /** - * The device machine id for the iPhone 14 Pro Max (iPhone15,3), used to get 60fps. - * The device machine id for the iPhone 15 Pro Max (iPhone16,2), used to get HDR with AV1 hardware decoding. - * - *

- * See this GitHub Gist for more - * information. - *

- */ - private static final String IOS_DEVICE_MODEL = deviceHasAV1HardwareDecoding() ? "iPhone16,2" : "iPhone15,3"; - private static final String IOS_OS_VERSION = "17.6.21G80"; - private static final String IOS_USER_AGENT_VERSION = "17_6"; - private static final String IOS_USER_AGENT = "com.google.ios.youtube/" + - IOS_CLIENT_VERSION + - "(" + - IOS_DEVICE_MODEL + - "; U; CPU iOS " + - IOS_USER_AGENT_VERSION + - " like Mac OS X)"; - - private static final String TVHTML5_SIMPLY_EMBEDDED_PLAYER_CLIENT_VERSION = "2.0"; - private static final String TVHTML5_SIMPLY_EMBEDDED_PLAYER_USER_AGENT = "Mozilla/5.0 (SMART-TV; LINUX; Tizen 6.5)" + - " AppleWebKit/537.36 (KHTML, like Gecko)" + - " 85.0.4183.93/6.5 TV Safari/537.36"; - private static final String WEB_CLIENT_VERSION = "2.20240726.00.00"; - private static final String WEB_OS_VERSION = "10"; - private static final String WEB_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64)" + - " AppleWebKit/537.36 (KHTML, like Gecko)" + - " Chrome/124.0.0.0 Mobile Safari/537.36"; - - private static final String ANDROID_INNER_TUBE_BODY; - private static final String ANDROID_EMBED_INNER_TUBE_BODY; - private static final String ANDROID_TESTSUITE_INNER_TUBE_BODY; - private static final String ANDROID_UNPLUGGED_INNER_TUBE_BODY; - private static final String ANDROID_VR_INNER_TUBE_BODY; - private static final String IOS_INNER_TUBE_BODY; - private static final String TVHTML5_SIMPLY_EMBED_INNER_TUBE_BODY; - private static final String WEB_INNER_TUBE_BODY; - - private static final String YT_API_URL = "https://www.youtube.com/youtubei/v1/"; - /** * TCP connection and HTTP read timeout */ private static final int CONNECTION_TIMEOUT_MILLISECONDS = 10 * 1000; // 10 Seconds. - static { - JSONObject androidInnerTubeBody = new JSONObject(); - JSONObject androidEmbedInnerTubeBody = new JSONObject(); - JSONObject androidTestsuiteInnerTubeBody = new JSONObject(); - JSONObject androidUnpluggedInnerTubeBody = new JSONObject(); - JSONObject androidVRInnerTubeBody = new JSONObject(); - JSONObject iOSInnerTubeBody = new JSONObject(); - JSONObject tvEmbedInnerTubeBody = new JSONObject(); - JSONObject webInnerTubeBody = new JSONObject(); - - try { - JSONObject context = new JSONObject(); - - JSONObject client = new JSONObject(); - client.put("clientName", "ANDROID"); - client.put("clientVersion", ANDROID_CLIENT_VERSION); - client.put("platform", "MOBILE"); - client.put("androidSdkVersion", ANDROID_OS_SDK_VERSION); - client.put("osName", "Android"); - client.put("osVersion", ANDROID_OS_RELEASE_VERSION); - - context.put("client", client); - - androidInnerTubeBody.put("context", context); - androidInnerTubeBody.put("videoId", "%s"); - } catch (JSONException e) { - Logger.printException(() -> "Failed to create Android innerTubeBody", e); - } - - ANDROID_INNER_TUBE_BODY = androidInnerTubeBody.toString(); - - try { - JSONObject context = new JSONObject(); - - JSONObject client = new JSONObject(); - client.put("clientName", "ANDROID_EMBEDDED_PLAYER"); - client.put("clientVersion", ANDROID_CLIENT_VERSION); - client.put("clientScreen", "EMBED"); - client.put("platform", "MOBILE"); - client.put("androidSdkVersion", ANDROID_OS_SDK_VERSION); - client.put("osName", "Android"); - client.put("osVersion", ANDROID_OS_RELEASE_VERSION); - - JSONObject thirdParty = new JSONObject(); - thirdParty.put("embedUrl", "https://www.youtube.com/embed/%s"); - - context.put("thirdParty", thirdParty); - context.put("client", client); - - androidEmbedInnerTubeBody.put("context", context); - androidEmbedInnerTubeBody.put("videoId", "%s"); - } catch (JSONException e) { - Logger.printException(() -> "Failed to create Android Embed innerTubeBody", e); - } - - ANDROID_EMBED_INNER_TUBE_BODY = androidEmbedInnerTubeBody.toString(); - - try { - JSONObject context = new JSONObject(); - - JSONObject client = new JSONObject(); - client.put("clientName", "ANDROID_TESTSUITE"); - client.put("clientVersion", ANDROID_TESTSUITE_CLIENT_VERSION); - client.put("platform", "MOBILE"); - client.put("androidSdkVersion", ANDROID_OS_SDK_VERSION); - client.put("osName", "Android"); - client.put("osVersion", ANDROID_OS_RELEASE_VERSION); - - context.put("client", client); - - androidTestsuiteInnerTubeBody.put("context", context); - androidTestsuiteInnerTubeBody.put("videoId", "%s"); - } catch (JSONException e) { - Logger.printException(() -> "Failed to create Android Testsuite innerTubeBody", e); - } - - ANDROID_TESTSUITE_INNER_TUBE_BODY = androidTestsuiteInnerTubeBody.toString(); - - try { - JSONObject context = new JSONObject(); - - JSONObject client = new JSONObject(); - client.put("clientName", "ANDROID_UNPLUGGED"); - client.put("clientVersion", ANDROID_UNPLUGGED_CLIENT_VERSION); - client.put("platform", "MOBILE"); - client.put("androidSdkVersion", ANDROID_UNPLUGGED_OS_SDK_VERSION); - client.put("osName", "Android"); - client.put("osVersion", ANDROID_UNPLUGGED_OS_RELEASE_VERSION); - - context.put("client", client); - - androidUnpluggedInnerTubeBody.put("context", context); - androidUnpluggedInnerTubeBody.put("videoId", "%s"); - } catch (JSONException e) { - Logger.printException(() -> "Failed to create Android Unplugged innerTubeBody", e); - } - - ANDROID_UNPLUGGED_INNER_TUBE_BODY = androidUnpluggedInnerTubeBody.toString(); - - try { - JSONObject context = new JSONObject(); - - JSONObject client = new JSONObject(); - client.put("clientName", "ANDROID_VR"); - client.put("clientVersion", ANDROID_VR_CLIENT_VERSION); - client.put("platform", "MOBILE"); - client.put("androidSdkVersion", ANDROID_VR_OS_SDK_VERSION); - client.put("osName", "Android"); - client.put("osVersion", ANDROID_VR_OS_RELEASE_VERSION); - - context.put("client", client); - - androidVRInnerTubeBody.put("context", context); - androidVRInnerTubeBody.put("videoId", "%s"); - } catch (JSONException e) { - Logger.printException(() -> "Failed to create Android VR innerTubeBody", e); - } - - ANDROID_VR_INNER_TUBE_BODY = androidVRInnerTubeBody.toString(); - - try { - JSONObject context = new JSONObject(); - - JSONObject client = new JSONObject(); - client.put("clientName", "IOS"); - client.put("clientVersion", IOS_CLIENT_VERSION); - client.put("deviceMake", "Apple"); - client.put("deviceModel", IOS_DEVICE_MODEL); - client.put("platform", "MOBILE"); - client.put("osName", "iOS"); - client.put("osVersion", IOS_OS_VERSION); - - context.put("client", client); - - iOSInnerTubeBody.put("context", context); - iOSInnerTubeBody.put("videoId", "%s"); - } catch (JSONException e) { - Logger.printException(() -> "Failed to create iOS innerTubeBody", e); - } - - IOS_INNER_TUBE_BODY = iOSInnerTubeBody.toString(); - - try { - JSONObject context = new JSONObject(); - - JSONObject client = new JSONObject(); - client.put("clientName", "TVHTML5_SIMPLY_EMBEDDED_PLAYER"); - client.put("clientVersion", TVHTML5_SIMPLY_EMBEDDED_PLAYER_CLIENT_VERSION); - client.put("platform", "TV"); - client.put("clientScreen", "EMBED"); - - JSONObject thirdParty = new JSONObject(); - thirdParty.put("embedUrl", "https://www.youtube.com/watch?v=%s"); - - context.put("thirdParty", thirdParty); - context.put("client", client); - - tvEmbedInnerTubeBody.put("context", context); - tvEmbedInnerTubeBody.put("videoId", "%s"); - } catch (JSONException e) { - Logger.printException(() -> "Failed to create TV Embed innerTubeBody", e); - } + private PlayerRoutes() { + } - TVHTML5_SIMPLY_EMBED_INNER_TUBE_BODY = tvEmbedInnerTubeBody.toString(); + static String createInnertubeBody(ClientType clientType) { + JSONObject innerTubeBody = new JSONObject(); try { JSONObject context = new JSONObject(); JSONObject client = new JSONObject(); - client.put("clientName", "WEB"); - client.put("clientVersion", WEB_CLIENT_VERSION); - client.put("clientScreen", "WATCH"); + client.put("clientName", clientType.name()); + client.put("clientVersion", clientType.appVersion); + client.put("deviceModel", clientType.model); + client.put("osVersion", clientType.osVersion); + if (clientType.make != null) { + client.put("deviceMake", clientType.make); + } + if (clientType.osName != null) { + client.put("osName", clientType.osName); + } + if (clientType.androidSdkVersion != null) { + client.put("androidSdkVersion", clientType.androidSdkVersion.toString()); + } context.put("client", client); - webInnerTubeBody.put("context", context); - webInnerTubeBody.put("videoId", "%s"); + innerTubeBody.put("context", context); + innerTubeBody.put("contentCheckOk", true); + innerTubeBody.put("racyCheckOk", true); + innerTubeBody.put("videoId", "%s"); } catch (JSONException e) { - Logger.printException(() -> "Failed to create Web innerTubeBody", e); - } - - WEB_INNER_TUBE_BODY = webInnerTubeBody.toString(); - } - - private static boolean deviceHasAV1HardwareDecoding() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - MediaCodecList codecList = new MediaCodecList(MediaCodecList.ALL_CODECS); - - for (MediaCodecInfo codecInfo : codecList.getCodecInfos()) { - if (codecInfo.isHardwareAccelerated() && !codecInfo.isEncoder()) { - String[] supportedTypes = codecInfo.getSupportedTypes(); - for (String type : supportedTypes) { - if (type.equalsIgnoreCase("video/av01")) { - MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type); - if (capabilities != null) { - Logger.printDebug(() -> "Device supports AV1 hardware decoding."); - return true; - } - } - } - } - } + Logger.printException(() -> "Failed to create innerTubeBody", e); } - Logger.printDebug(() -> "Device does not support AV1 hardware decoding."); - return false; - } - - private PlayerRoutes() { + return innerTubeBody.toString(); } /** * @noinspection SameParameterValue */ - public static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, String userAgent) throws IOException { - var connection = Requester.getConnectionFromCompiledRoute(YT_API_URL, route); + static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.CompiledRoute route, ClientType clientType) throws IOException { + var connection = Requester.getConnectionFromCompiledRoute(YOUTUBEI_V1_GAPIS_URL, route); - connection.setRequestProperty("User-Agent", userAgent); - connection.setRequestProperty("X-Goog-Api-Format-Version", "2"); connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", clientType.userAgent); connection.setUseCaches(false); connection.setDoOutput(true); @@ -390,36 +82,4 @@ public static HttpURLConnection getPlayerResponseConnectionFromRoute(Route.Compi connection.setReadTimeout(CONNECTION_TIMEOUT_MILLISECONDS); return connection; } - - public enum ClientType { - ANDROID(3, ANDROID_DEVICE_MODEL, ANDROID_CLIENT_VERSION, ANDROID_INNER_TUBE_BODY, ANDROID_OS_RELEASE_VERSION, ANDROID_USER_AGENT), - ANDROID_EMBEDDED_PLAYER(55, ANDROID_DEVICE_MODEL, ANDROID_CLIENT_VERSION, ANDROID_EMBED_INNER_TUBE_BODY, ANDROID_OS_RELEASE_VERSION, ANDROID_USER_AGENT), - ANDROID_TESTSUITE(30, ANDROID_DEVICE_MODEL, ANDROID_TESTSUITE_CLIENT_VERSION, ANDROID_TESTSUITE_INNER_TUBE_BODY, ANDROID_OS_RELEASE_VERSION, ANDROID_USER_AGENT), - ANDROID_UNPLUGGED(29, ANDROID_UNPLUGGED_DEVICE_MODEL, ANDROID_UNPLUGGED_CLIENT_VERSION, ANDROID_UNPLUGGED_INNER_TUBE_BODY, ANDROID_UNPLUGGED_OS_RELEASE_VERSION, ANDROID_UNPLUGGED_USER_AGENT), - ANDROID_VR(28, ANDROID_VR_DEVICE_MODEL, ANDROID_VR_CLIENT_VERSION, ANDROID_VR_INNER_TUBE_BODY, ANDROID_VR_OS_RELEASE_VERSION, ANDROID_VR_USER_AGENT), - IOS(5, IOS_DEVICE_MODEL, IOS_CLIENT_VERSION, IOS_INNER_TUBE_BODY, IOS_OS_VERSION, IOS_USER_AGENT), - // No suitable model name was found for TVHTML5_SIMPLY_EMBEDDED_PLAYER. Use the model name of ANDROID. - TVHTML5_SIMPLY_EMBEDDED_PLAYER(85, ANDROID_DEVICE_MODEL, TVHTML5_SIMPLY_EMBEDDED_PLAYER_CLIENT_VERSION, TVHTML5_SIMPLY_EMBED_INNER_TUBE_BODY, TVHTML5_SIMPLY_EMBEDDED_PLAYER_CLIENT_VERSION, TVHTML5_SIMPLY_EMBEDDED_PLAYER_USER_AGENT), - // No suitable model name was found for WEB. Use the model name of ANDROID. - WEB(1, ANDROID_DEVICE_MODEL, WEB_CLIENT_VERSION, WEB_INNER_TUBE_BODY, WEB_OS_VERSION, WEB_USER_AGENT); - - public final String friendlyName; - public final int id; - public final String model; - public final String version; - public final String innerTubeBody; - public final String osVersion; - public final String userAgent; - - ClientType(int id, String model, String version, String innerTubeBody, - String osVersion, String userAgent) { - this.friendlyName = str("revanced_spoof_client_options_entry_" + name().toLowerCase()); - this.id = id; - this.model = model; - this.version = version; - this.innerTubeBody = innerTubeBody; - this.osVersion = osVersion; - this.userAgent = userAgent; - } - } } \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/StoryboardRendererRequester.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/StoryboardRendererRequester.java deleted file mode 100644 index 75b1b28aa6..0000000000 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/StoryboardRendererRequester.java +++ /dev/null @@ -1,184 +0,0 @@ -package app.revanced.integrations.youtube.patches.misc.requests; - -import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.GET_STORYBOARD_SPEC_RENDERER; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.SocketTimeoutException; -import java.nio.charset.StandardCharsets; -import java.util.Objects; - -import app.revanced.integrations.shared.requests.Requester; -import app.revanced.integrations.shared.utils.Logger; -import app.revanced.integrations.shared.utils.Utils; -import app.revanced.integrations.youtube.patches.misc.StoryboardRenderer; -import app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.ClientType; - -public class StoryboardRendererRequester { - - private StoryboardRendererRequester() { - } - - private static void handleConnectionError(@NonNull String toastMessage, @Nullable Exception ex) { - Logger.printInfo(() -> toastMessage, ex); - } - - @Nullable - private static JSONObject fetchPlayerResponse(@NonNull String requestBody, - @NonNull String userAgent) { - final long startTime = System.currentTimeMillis(); - try { - Utils.verifyOffMainThread(); - Objects.requireNonNull(requestBody); - - final byte[] innerTubeBody = requestBody.getBytes(StandardCharsets.UTF_8); - - HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STORYBOARD_SPEC_RENDERER, userAgent); - connection.getOutputStream().write(innerTubeBody, 0, innerTubeBody.length); - - final int responseCode = connection.getResponseCode(); - if (responseCode == 200) return Requester.parseJSONObject(connection); - - // Always show a toast for this, as a non 200 response means something is broken. - handleConnectionError("Spoof storyboard not available: " + responseCode, null); - connection.disconnect(); - } catch (SocketTimeoutException ex) { - handleConnectionError("Spoof storyboard temporarily not available (API timed out)", ex); - } catch (IOException ex) { - handleConnectionError("Spoof storyboard temporarily not available: " + ex.getMessage(), ex); - } catch (Exception ex) { - Logger.printException(() -> "Spoof storyboard fetch failed", ex); // Should never happen. - } finally { - Logger.printDebug(() -> "Request took: " + (System.currentTimeMillis() - startTime) + "ms"); - } - - return null; - } - - private static String getPlayabilityStatus(@NonNull JSONObject playerResponse) { - try { - return playerResponse.getJSONObject("playabilityStatus").getString("status"); - } catch (JSONException e) { - Logger.printDebug(() -> "Failed to get playabilityStatus for response: " + playerResponse); - } - - // Prevent NullPointerException - return ""; - } - - /** - * Fetches the storyboardRenderer from the innerTubeBody. - * - * @param innerTubeBody The innerTubeBody to use to fetch the storyboardRenderer. - * @return StoryboardRenderer or null if playabilityStatus is not OK. - */ - @Nullable - private static StoryboardRenderer getStoryboardRendererUsingBody(@NonNull String videoId, - @NonNull String innerTubeBody, - @NonNull String userAgent) { - final JSONObject playerResponse = fetchPlayerResponse(innerTubeBody, userAgent); - if (playerResponse == null) - return null; - - final String playabilityStatus = getPlayabilityStatus(playerResponse); - if (playabilityStatus.equals("OK")) - return getStoryboardRendererUsingResponse(videoId, playerResponse); - - // Get the StoryboardRenderer from Premieres Video. - // In Android client, YouTube used weird base64-like encoding for PlayerResponse. - // So additional fetching with WEB client is required for getting unSerialized ones. - if (playabilityStatus.equals("LIVE_STREAM_OFFLINE")) - return getTrailerStoryboardRenderer(videoId); - return null; - } - - @Nullable - private static StoryboardRenderer getTrailerStoryboardRenderer(@NonNull String videoId) { - try { - final ClientType requestClient = ClientType.WEB; - final JSONObject playerResponse = fetchPlayerResponse(String.format(requestClient.innerTubeBody, videoId), requestClient.userAgent); - - if (playerResponse == null) - return null; - - JSONObject unSerializedPlayerResponse = playerResponse.getJSONObject("playabilityStatus") - .getJSONObject("errorScreen").getJSONObject("ypcTrailerRenderer").getJSONObject("unserializedPlayerResponse"); - - if (getPlayabilityStatus(unSerializedPlayerResponse).equals("OK")) - return getStoryboardRendererUsingResponse(videoId, unSerializedPlayerResponse); - return null; - } catch (JSONException e) { - Logger.printException(() -> "Failed to get unserializedPlayerResponse", e); - } - - return null; - } - - @Nullable - private static StoryboardRenderer getStoryboardRendererUsingResponse(@NonNull String videoId, @NonNull JSONObject playerResponse) { - try { - Logger.printDebug(() -> "Parsing storyboardRenderer from response: " + playerResponse); - if (!playerResponse.has("storyboards")) { - Logger.printDebug(() -> "Using empty storyboard"); - return new StoryboardRenderer(videoId, null, false, null); - } - final JSONObject storyboards = playerResponse.getJSONObject("storyboards"); - final boolean isLiveStream = storyboards.has("playerLiveStoryboardSpecRenderer"); - final String storyboardsRendererTag = isLiveStream - ? "playerLiveStoryboardSpecRenderer" - : "playerStoryboardSpecRenderer"; - - final var rendererElement = storyboards.getJSONObject(storyboardsRendererTag); - StoryboardRenderer renderer = new StoryboardRenderer( - videoId, - rendererElement.getString("spec"), - isLiveStream, - rendererElement.has("recommendedLevel") - ? rendererElement.getInt("recommendedLevel") - : null - ); - - Logger.printDebug(() -> "Fetched: " + renderer); - - return renderer; - } catch (JSONException e) { - Logger.printException(() -> "Failed to get storyboardRenderer", e); - } - - return null; - } - - @Nullable - public static StoryboardRenderer getStoryboardRenderer(@NonNull String videoId) { - Objects.requireNonNull(videoId); - - // Fetch with iOS. - ClientType clientType = ClientType.IOS; - StoryboardRenderer renderer = getStoryboardRendererUsingBody( - videoId, - String.format(clientType.innerTubeBody, videoId), - clientType.userAgent - ); - if (renderer == null) { - Logger.printDebug(() -> videoId + " not available using iOS client"); - - clientType = ClientType.TVHTML5_SIMPLY_EMBEDDED_PLAYER; - renderer = getStoryboardRendererUsingBody( - videoId, - String.format(clientType.innerTubeBody, videoId, videoId), - clientType.userAgent - ); - if (renderer == null) { - Logger.printDebug(() -> videoId + " not available using TV html5 embedded client"); - } - } - - return renderer; - } -} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/StreamingDataRequest.java b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/StreamingDataRequest.java new file mode 100644 index 0000000000..b659af7b44 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/misc/requests/StreamingDataRequest.java @@ -0,0 +1,243 @@ +package app.revanced.integrations.youtube.patches.misc.requests; + +import static app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.GET_STREAMING_DATA; + +import android.annotation.SuppressLint; +import android.os.Build; + +import androidx.annotation.GuardedBy; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.SocketTimeoutException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import app.revanced.integrations.shared.utils.Logger; +import app.revanced.integrations.shared.utils.Utils; +import app.revanced.integrations.youtube.patches.misc.client.AppClient.ClientType; +import app.revanced.integrations.youtube.settings.Settings; + +public class StreamingDataRequest { + private static final ClientType[] allClientTypes = { + ClientType.IOS, + ClientType.ANDROID_VR, + ClientType.ANDROID_UNPLUGGED, + ClientType.ANDROID_TESTSUITE, + ClientType.ANDROID_EMBEDDED_PLAYER, + ClientType.WEB, + ClientType.TVHTML5_SIMPLY_EMBEDDED_PLAYER + }; + + private static final ClientType[] clientTypesToUse; + + static { + final ClientType clientType = Settings.SPOOF_STREAMING_DATA_TYPE.get(); + ClientType[] clientTypeArray = new ClientType[allClientTypes.length + 1]; + clientTypeArray[0] = clientType; + int i = 1; + for (ClientType c : allClientTypes) { + clientTypeArray[i] = c; + i++; + } + clientTypeArray = Arrays.stream(clientTypeArray) + .distinct() + .toArray(ClientType[]::new); + clientTypesToUse = Arrays.copyOfRange(clientTypeArray, 0, 3); + } + + private static String lastSpoofedClientName = "Unknown"; + + public static String getLastSpoofedClientName() { + return lastSpoofedClientName; + } + + /** + * How long to keep fetches until they are expired. + */ + private static final long CACHE_RETENTION_TIME_MILLISECONDS = 60 * 1000; // 1 Minute + + private static final long MAX_MILLISECONDS_TO_WAIT_FOR_FETCH = 20 * 1000; // 20 seconds + + @GuardedBy("itself") + private static final Map cache = new HashMap<>(); + + @SuppressLint("ObsoleteSdkInt") + public static void fetchRequestIfNeeded(@Nullable String videoId, Map fetchHeaders) { + Objects.requireNonNull(videoId); + synchronized (cache) { + final long now = System.currentTimeMillis(); + + // Remove any expired entries. + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + cache.values().removeIf(request -> { + final boolean expired = request.isExpired(now); + if (expired) Logger.printDebug(() -> "Removing expired stream: " + request.videoId); + return expired; + }); + } else { + for (Iterator> it = cache.entrySet().iterator(); it.hasNext();) { + final Map.Entry entry = it.next(); + final StreamingDataRequest request = entry.getValue(); + final boolean expired = request.isExpired(now); + if (expired) { + Logger.printDebug(() -> "Removing expired stream: " + request.videoId); + it.remove(); + } + } + } + + if (!cache.containsKey(videoId)) { + cache.put(videoId, new StreamingDataRequest(videoId, fetchHeaders)); + } + } + } + + @Nullable + public static StreamingDataRequest getRequestForVideoId(@Nullable String videoId) { + synchronized (cache) { + return cache.get(videoId); + } + } + + private static void handleConnectionError(String toastMessage, @Nullable Exception ex) { + Logger.printInfo(() -> toastMessage, ex); + } + + @Nullable + private static HttpURLConnection send(ClientType clientType, String videoId, + Map playerHeaders) { + Objects.requireNonNull(clientType); + Objects.requireNonNull(videoId); + Objects.requireNonNull(playerHeaders); + + final long startTime = System.currentTimeMillis(); + String clientTypeName = clientType.name(); + Logger.printDebug(() -> "Fetching video streams for: " + videoId + " using client: " + clientType.name()); + + try { + HttpURLConnection connection = PlayerRoutes.getPlayerResponseConnectionFromRoute(GET_STREAMING_DATA, clientType); + + String authHeader = playerHeaders.get("Authorization"); + String visitorId = playerHeaders.get("X-Goog-Visitor-Id"); + connection.setRequestProperty("Authorization", authHeader); + connection.setRequestProperty("X-Goog-Visitor-Id", visitorId); + + String innerTubeBody = String.format(PlayerRoutes.createInnertubeBody(clientType), videoId); + byte[] requestBody = innerTubeBody.getBytes(StandardCharsets.UTF_8); + connection.setFixedLengthStreamingMode(requestBody.length); + connection.getOutputStream().write(requestBody); + + final int responseCode = connection.getResponseCode(); + if (responseCode == 200) return connection; + + handleConnectionError(clientTypeName + " not available with response code: " + + responseCode + " message: " + connection.getResponseMessage(), + null); + } catch (SocketTimeoutException ex) { + handleConnectionError("Connection timeout", ex); + } catch (IOException ex) { + handleConnectionError("Network error", ex); + } catch (Exception ex) { + Logger.printException(() -> "send failed", ex); + } finally { + Logger.printDebug(() -> "video: " + videoId + " took: " + (System.currentTimeMillis() - startTime) + "ms"); + } + + return null; + } + + private static ByteBuffer fetch(@NonNull String videoId, Map playerHeaders) { + // Retry with different client if empty response body is received. + for (ClientType clientType : clientTypesToUse) { + HttpURLConnection connection = send(clientType, videoId, playerHeaders); + if (connection != null) { + try { + // gzip encoding doesn't response with content length (-1), + // but empty response body does. + if (connection.getContentLength() != 0) { + try (InputStream inputStream = new BufferedInputStream(connection.getInputStream())) { + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + byte[] buffer = new byte[8192]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) >= 0) { + baos.write(buffer, 0, bytesRead); + } + + lastSpoofedClientName = clientType.friendlyName; + + return ByteBuffer.wrap(baos.toByteArray()); + } + } + } + } catch (IOException ex) { + Logger.printException(() -> "Fetch failed while processing response data", ex); + } + } + } + + handleConnectionError("Could not fetch any client streams", null); + return null; + } + + /** + * Time this instance and the fetch future was created. + */ + private final long timeFetched; + private final String videoId; + private final Future future; + + private StreamingDataRequest(String videoId, Map playerHeaders) { + Objects.requireNonNull(playerHeaders); + this.timeFetched = System.currentTimeMillis(); + this.videoId = videoId; + this.future = Utils.submitOnBackgroundThread(() -> fetch(videoId, playerHeaders)); + } + + public boolean isExpired(long now) { + final long timeSinceCreation = now - timeFetched; + if (timeSinceCreation > CACHE_RETENTION_TIME_MILLISECONDS) { + return true; + } + + // Only expired if the fetch failed (API null response). + return (fetchCompleted() && getStream() == null); + } + + /** + * @return if the RYD fetch call has completed. + */ + public boolean fetchCompleted() { + return future.isDone(); + } + + @Nullable + public ByteBuffer getStream() { + try { + return future.get(MAX_MILLISECONDS_TO_WAIT_FOR_FETCH, TimeUnit.MILLISECONDS); + } catch (TimeoutException ex) { + Logger.printInfo(() -> "getStream timed out", ex); + } catch (InterruptedException ex) { + Logger.printException(() -> "getStream interrupted", ex); + Thread.currentThread().interrupt(); // Restore interrupt status flag. + } catch (ExecutionException ex) { + Logger.printException(() -> "getStream failure", ex); + } + + return null; + } +} diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PatchStatus.java b/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PatchStatus.java index 9d951380a1..5e73a29db1 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PatchStatus.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/utils/PatchStatus.java @@ -27,11 +27,6 @@ public static boolean SponsorBlock() { return false; } - public static boolean SpoofClient() { - // Replace this with true if the Spoof client patch succeeds - return false; - } - public static boolean ToolBarComponents() { // Replace this with true if the Toolbar components patch succeeds return false; diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/video/PlaybackSpeedPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/video/PlaybackSpeedPatch.java index aa6a20e725..41aea44a95 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/video/PlaybackSpeedPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/video/PlaybackSpeedPatch.java @@ -69,7 +69,7 @@ public static void userSelectedPlaybackSpeed(float playbackSpeed) { Settings.DEFAULT_PLAYBACK_SPEED.save(playbackSpeed); - if (!Settings.SHOW_TOAST_ON_DEFAULT_SPEED_CHANGE.get()) + if (!Settings.REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST.get()) return; showToastShort(str("revanced_remember_playback_speed_toast", playbackSpeed + "x")); diff --git a/app/src/main/java/app/revanced/integrations/youtube/patches/video/VideoQualityPatch.java b/app/src/main/java/app/revanced/integrations/youtube/patches/video/VideoQualityPatch.java index a517a4e5f9..0eba5b1ddd 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/patches/video/VideoQualityPatch.java +++ b/app/src/main/java/app/revanced/integrations/youtube/patches/video/VideoQualityPatch.java @@ -83,7 +83,7 @@ private static void userSelectedVideoQuality(final int defaultQuality) { default -> wifiQualitySetting.save(defaultQuality); } - if (!Settings.SHOW_TOAST_ON_DEFAULT_QUALITY_CHANGE.get()) + if (!Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) return; Utils.showToastShort(str("revanced_remember_video_quality_" + networkType.getName(), defaultQuality + "p")); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java index 8445e78971..45c9de6aab 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/Settings.java @@ -31,8 +31,9 @@ import app.revanced.integrations.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.StillImagesAvailability; import app.revanced.integrations.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.ThumbnailOption; import app.revanced.integrations.youtube.patches.alternativethumbnails.AlternativeThumbnailsPatch.ThumbnailStillTime; +import app.revanced.integrations.youtube.patches.misc.SpoofStreamingDataPatch; import app.revanced.integrations.youtube.patches.misc.WatchHistoryPatch.WatchHistoryType; -import app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.ClientType; +import app.revanced.integrations.youtube.patches.misc.client.AppClient.ClientType; import app.revanced.integrations.youtube.patches.shorts.AnimationFeedbackPatch.AnimationType; import app.revanced.integrations.youtube.sponsorblock.SponsorBlockSettings; @@ -264,11 +265,11 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_REPORT = new BooleanSetting("revanced_hide_player_flyout_menu_report", TRUE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_ADDITIONAL_SETTINGS = new BooleanSetting("revanced_hide_player_flyout_menu_additional_settings", FALSE); - public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_AMBIENT = new BooleanSetting("revanced_hide_player_flyout_menu_ambient_mode", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_HELP = new BooleanSetting("revanced_hide_player_flyout_menu_help", TRUE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_LOOP = new BooleanSetting("revanced_hide_player_flyout_menu_loop_video", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_PIP = new BooleanSetting("revanced_hide_player_flyout_menu_pip", TRUE, true); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_PREMIUM_CONTROLS = new BooleanSetting("revanced_hide_player_flyout_menu_premium_controls", TRUE); + public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_SLEEP_TIMER = new BooleanSetting("revanced_hide_player_flyout_menu_sleep_timer", TRUE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_STABLE_VOLUME = new BooleanSetting("revanced_hide_player_flyout_menu_stable_volume", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_STATS_FOR_NERDS = new BooleanSetting("revanced_hide_player_flyout_menu_stats_for_nerds", FALSE); public static final BooleanSetting HIDE_PLAYER_FLYOUT_MENU_WATCH_IN_VR = new BooleanSetting("revanced_hide_player_flyout_menu_watch_in_vr", TRUE); @@ -322,7 +323,7 @@ public class Settings extends BaseSettings { public static final BooleanSetting OVERLAY_BUTTON_COPY_VIDEO_URL_TIMESTAMP = new BooleanSetting("revanced_overlay_button_copy_video_url_timestamp", FALSE); public static final BooleanSetting OVERLAY_BUTTON_MUTE_VOLUME = new BooleanSetting("revanced_overlay_button_mute_volume", FALSE); public static final BooleanSetting OVERLAY_BUTTON_EXTERNAL_DOWNLOADER = new BooleanSetting("revanced_overlay_button_external_downloader", FALSE); - public static final BooleanSetting OVERLAY_BUTTON_SPEED_DIALOG = new BooleanSetting("revanced_overlay_button_speed_dialog", TRUE); + public static final BooleanSetting OVERLAY_BUTTON_SPEED_DIALOG = new BooleanSetting("revanced_overlay_button_speed_dialog", FALSE); public static final BooleanSetting OVERLAY_BUTTON_TIME_ORDERED_PLAYLIST = new BooleanSetting("revanced_overlay_button_time_ordered_playlist", FALSE); public static final BooleanSetting OVERLAY_BUTTON_WHITELIST = new BooleanSetting("revanced_overlay_button_whitelist", FALSE); @@ -357,6 +358,7 @@ public class Settings extends BaseSettings { // PreferenceScreen: Shorts public static final BooleanSetting DISABLE_RESUMING_SHORTS_PLAYER = new BooleanSetting("revanced_disable_resuming_shorts_player", TRUE); + public static final BooleanSetting HIDE_SHORTS_FLOATING_BUTTON = new BooleanSetting("revanced_hide_shorts_floating_button", TRUE); public static final BooleanSetting HIDE_SHORTS_SHELF = new BooleanSetting("revanced_hide_shorts_shelf", TRUE, true); public static final BooleanSetting HIDE_SHORTS_SHELF_HOME_RELATED_VIDEOS = new BooleanSetting("revanced_hide_shorts_shelf_home_related_videos", TRUE, true); public static final BooleanSetting HIDE_SHORTS_SHELF_SUBSCRIPTIONS = new BooleanSetting("revanced_hide_shorts_shelf_subscriptions", TRUE, true); @@ -370,12 +372,16 @@ public class Settings extends BaseSettings { public static final BooleanSetting HIDE_SHORTS_PAUSED_HEADER = new BooleanSetting("revanced_hide_shorts_paused_header", FALSE, true); public static final BooleanSetting HIDE_SHORTS_PAUSED_OVERLAY_BUTTONS = new BooleanSetting("revanced_hide_shorts_paused_overlay_buttons", FALSE); public static final BooleanSetting HIDE_SHORTS_PAID_PROMOTION_LABEL = new BooleanSetting("revanced_hide_shorts_paid_promotion_label", TRUE, true); + public static final BooleanSetting HIDE_SHORTS_TRENDS_BUTTON = new BooleanSetting("revanced_hide_shorts_trends_button", TRUE); + public static final BooleanSetting HIDE_SHORTS_SHOPPING_BUTTON = new BooleanSetting("revanced_hide_shorts_shopping_button", TRUE); public static final BooleanSetting HIDE_SHORTS_SHOP_BUTTON = new BooleanSetting("revanced_hide_shorts_shop_button", TRUE); public static final BooleanSetting HIDE_SHORTS_TAGGED_PRODUCTS = new BooleanSetting("revanced_hide_shorts_tagged_products", TRUE); - public static final BooleanSetting HIDE_SHORTS_LOCATION_LABEL = new BooleanSetting("revanced_hide_shorts_location_label", TRUE); + public static final BooleanSetting HIDE_SHORTS_LOCATION_BUTTON = new BooleanSetting("revanced_hide_shorts_location_button", TRUE); public static final BooleanSetting HIDE_SHORTS_SAVE_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_save_sound_button", TRUE); - public static final BooleanSetting HIDE_SHORTS_SEARCH_SUGGESTIONS = new BooleanSetting("revanced_hide_shorts_search_suggestions", TRUE); + public static final BooleanSetting HIDE_SHORTS_SEARCH_SUGGESTIONS_BUTTON = new BooleanSetting("revanced_hide_shorts_search_suggestions_button", TRUE); public static final BooleanSetting HIDE_SHORTS_SUPER_THANKS_BUTTON = new BooleanSetting("revanced_hide_shorts_super_thanks_button", TRUE); + public static final BooleanSetting HIDE_SHORTS_USE_THIS_SOUND_BUTTON = new BooleanSetting("revanced_hide_shorts_use_this_sound_button", TRUE); + public static final BooleanSetting HIDE_SHORTS_USE_TEMPLATE_BUTTON = new BooleanSetting("revanced_hide_shorts_use_template_button", TRUE); public static final BooleanSetting HIDE_SHORTS_INFO_PANEL = new BooleanSetting("revanced_hide_shorts_info_panel", TRUE); public static final BooleanSetting HIDE_SHORTS_LIVE_HEADER = new BooleanSetting("revanced_hide_shorts_live_header", FALSE); public static final BooleanSetting HIDE_SHORTS_CHANNEL_BAR = new BooleanSetting("revanced_hide_shorts_channel_bar", FALSE); @@ -438,9 +444,9 @@ public class Settings extends BaseSettings { public static final BooleanSetting CUSTOM_PLAYBACK_SPEED_MENU_TYPE = new BooleanSetting("revanced_custom_playback_speed_menu_type", FALSE, parent(ENABLE_CUSTOM_PLAYBACK_SPEED)); public static final StringSetting CUSTOM_PLAYBACK_SPEEDS = new StringSetting("revanced_custom_playback_speeds", "0.25\n0.5\n0.75\n1.0\n1.25\n1.5\n1.75\n2.0\n2.25\n2.5", true, parent(ENABLE_CUSTOM_PLAYBACK_SPEED)); public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED = new BooleanSetting("revanced_remember_playback_speed_last_selected", TRUE); - public static final BooleanSetting SHOW_TOAST_ON_DEFAULT_SPEED_CHANGE = new BooleanSetting("revanced_show_toast_remember_video_speed", TRUE); + public static final BooleanSetting REMEMBER_PLAYBACK_SPEED_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_playback_speed_last_selected_toast", TRUE, parent(REMEMBER_PLAYBACK_SPEED_LAST_SELECTED)); public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED = new BooleanSetting("revanced_remember_video_quality_last_selected", TRUE); - public static final BooleanSetting SHOW_TOAST_ON_DEFAULT_QUALITY_CHANGE = new BooleanSetting("revanced_show_toast_remember_video_quality", TRUE); + public static final BooleanSetting REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST = new BooleanSetting("revanced_remember_video_quality_last_selected_toast", TRUE, parent(REMEMBER_VIDEO_QUALITY_LAST_SELECTED)); public static final BooleanSetting RESTORE_OLD_VIDEO_QUALITY_MENU = new BooleanSetting("revanced_restore_old_video_quality_menu", TRUE, true); // Experimental Flags public static final BooleanSetting ENABLE_DEFAULT_PLAYBACK_SPEED_SHORTS = new BooleanSetting("revanced_enable_default_playback_speed_shorts", FALSE); @@ -455,18 +461,11 @@ public class Settings extends BaseSettings { public static final BooleanSetting ENABLE_EXTERNAL_BROWSER = new BooleanSetting("revanced_enable_external_browser", TRUE, true); public static final BooleanSetting ENABLE_OPEN_LINKS_DIRECTLY = new BooleanSetting("revanced_enable_open_links_directly", TRUE); public static final BooleanSetting DISABLE_QUIC_PROTOCOL = new BooleanSetting("revanced_disable_quic_protocol", FALSE, true); - public static final BooleanSetting SPOOF_CLIENT = new BooleanSetting("revanced_spoof_client", FALSE, true, "revanced_spoof_client_user_dialog_message"); - public static final BooleanSetting SPOOF_CLIENT_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_client_stats_for_nerds", TRUE, parent(SPOOF_CLIENT)); - public static final EnumSetting SPOOF_CLIENT_GENERAL = new EnumSetting<>("revanced_spoof_client_general", - ClientType.IOS); - public static final EnumSetting SPOOF_CLIENT_LIVESTREAM = new EnumSetting<>("revanced_spoof_client_livestream", - ClientType.IOS); - public static final EnumSetting SPOOF_CLIENT_SHORTS = new EnumSetting<>("revanced_spoof_client_shorts", - ClientType.IOS); - public static final EnumSetting SPOOF_CLIENT_FALLBACK = new EnumSetting<>("revanced_spoof_client_fallback", - // Some private videos cannot be played with {@code ClientType.IOS}. - // Use {@code ClientType.ANDROID_TESTSUITE}. - ClientType.ANDROID_TESTSUITE); + public static final BooleanSetting SPOOF_STREAMING_DATA = new BooleanSetting("revanced_spoof_streaming_data", TRUE, true, "revanced_spoof_streaming_data_user_dialog_message"); + public static final BooleanSetting SPOOF_STREAMING_DATA_IOS_FORCE_AVC = new BooleanSetting("revanced_spoof_streaming_data_ios_force_avc", FALSE, true, + "revanced_spoof_streaming_data_ios_force_avc_user_dialog_message", new SpoofStreamingDataPatch.ForceiOSAVCAvailability()); + public static final EnumSetting SPOOF_STREAMING_DATA_TYPE = new EnumSetting<>("revanced_spoof_streaming_data_type", ClientType.ANDROID_VR, true, parent(SPOOF_STREAMING_DATA)); + public static final BooleanSetting SPOOF_STREAMING_DATA_STATS_FOR_NERDS = new BooleanSetting("revanced_spoof_streaming_data_stats_for_nerds", TRUE, parent(SPOOF_STREAMING_DATA)); public static final EnumSetting WATCH_HISTORY_TYPE = new EnumSetting<>("revanced_watch_history_type", WatchHistoryType.REPLACE); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderPlaylistPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderPlaylistPreference.java index 295cb49f3b..e1f1ba3dcb 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderPlaylistPreference.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderPlaylistPreference.java @@ -25,7 +25,7 @@ import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.youtube.utils.ExtendedUtils; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "deprecation"}) public class ExternalDownloaderPlaylistPreference extends Preference implements Preference.OnPreferenceClickListener { private static final StringSetting settings = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME_PLAYLIST; diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderVideoPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderVideoPreference.java index 098d498a51..db59b317f7 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderVideoPreference.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ExternalDownloaderVideoPreference.java @@ -25,7 +25,7 @@ import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.youtube.utils.ExtendedUtils; -@SuppressWarnings("unused") +@SuppressWarnings({"unused", "deprecation"}) public class ExternalDownloaderVideoPreference extends Preference implements Preference.OnPreferenceClickListener { private static final StringSetting settings = Settings.EXTERNAL_DOWNLOADER_PACKAGE_NAME_VIDEO; diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/HtmlPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/HtmlPreference.java new file mode 100644 index 0000000000..35b41c7094 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/HtmlPreference.java @@ -0,0 +1,34 @@ +package app.revanced.integrations.youtube.settings.preference; + +import static android.text.Html.FROM_HTML_MODE_COMPACT; + +import android.content.Context; +import android.preference.Preference; +import android.text.Html; +import android.util.AttributeSet; + +/** + * Allows using basic html for the summary text. + */ +@SuppressWarnings({"unused", "deprecation"}) +public class HtmlPreference extends Preference { + { + setSummary(Html.fromHtml(getSummary().toString(), FROM_HTML_MODE_COMPACT)); + } + + public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public HtmlPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public HtmlPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public HtmlPreference(Context context) { + super(context); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ImportExportPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ImportExportPreference.java index 1a53dd1f8b..dbf4b1b6c4 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ImportExportPreference.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ImportExportPreference.java @@ -17,9 +17,7 @@ import app.revanced.integrations.shared.utils.Logger; import app.revanced.integrations.shared.utils.Utils; -/** - * @noinspection deprecation, unused - */ +@SuppressWarnings({"unused", "deprecation"}) public class ImportExportPreference extends EditTextPreference implements Preference.OnPreferenceClickListener { private String existingSettings; diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/OpenDefaultAppSettingsPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/OpenDefaultAppSettingsPreference.java index 0a8b608614..9870aac692 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/OpenDefaultAppSettingsPreference.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/OpenDefaultAppSettingsPreference.java @@ -10,9 +10,7 @@ import app.revanced.integrations.shared.utils.Logger; import app.revanced.integrations.shared.utils.Utils; -/** - * @noinspection ALL - */ +@SuppressWarnings({"unused", "deprecation"}) public class OpenDefaultAppSettingsPreference extends Preference { { setOnPreferenceClickListener(pref -> { @@ -22,10 +20,6 @@ public class OpenDefaultAppSettingsPreference extends Preference { final Intent intent = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? new Intent(android.provider.Settings.ACTION_APP_OPEN_BY_DEFAULT_SETTINGS, uri) : new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS, uri); - if (context == null) { - context = Utils.getContext(); - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } context.startActivity(intent); } catch (Exception exception) { Logger.printException(() -> "OpenDefaultAppSettings Failed"); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java index 7f58dfc446..85a68b4227 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedPreferenceFragment.java @@ -116,8 +116,7 @@ public class ReVancedPreferenceFragment extends PreferenceFragment { return; } - final Activity mActivity = getActivity(); - ReVancedSettingsPreference.initializeReVancedSettings(mActivity); + ReVancedSettingsPreference.initializeReVancedSettings(); if (settingImportInProgress) { return; @@ -293,7 +292,7 @@ public void onCreate(Bundle bundle) { setPreferenceScreenToolbar(); // Initialize ReVanced settings - ReVancedSettingsPreference.initializeReVancedSettings(getActivity()); + ReVancedSettingsPreference.initializeReVancedSettings(); // Import/export setBackupRestorePreference(); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedSettingsPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedSettingsPreference.java index 984f97e609..6d8a09281d 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedSettingsPreference.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/ReVancedSettingsPreference.java @@ -4,21 +4,16 @@ import static app.revanced.integrations.youtube.patches.general.MiniplayerPatch.MiniplayerType.MODERN_3; import static app.revanced.integrations.youtube.utils.ExtendedUtils.isSpoofingToLessThan; -import android.app.Activity; import android.os.Build; import android.preference.Preference; -import androidx.annotation.NonNull; - import app.revanced.integrations.shared.settings.Setting; import app.revanced.integrations.youtube.patches.general.MiniplayerPatch; import app.revanced.integrations.youtube.patches.utils.PatchStatus; import app.revanced.integrations.youtube.settings.Settings; import app.revanced.integrations.youtube.utils.ExtendedUtils; -/** - * @noinspection ALL - */ +@SuppressWarnings("deprecation") public class ReVancedSettingsPreference extends ReVancedPreferenceFragment { private static void enableDisablePreferences() { @@ -42,7 +37,7 @@ private static void enableDisablePreferences(final boolean isAvailable, final Se } } - public static void initializeReVancedSettings(@NonNull Activity activity) { + public static void initializeReVancedSettings() { enableDisablePreferences(); AmbientModePreferenceLinks(); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/SponsorBlockPreferenceFragment.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/SponsorBlockPreferenceFragment.java index 41b786fb89..d54832589d 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/SponsorBlockPreferenceFragment.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/SponsorBlockPreferenceFragment.java @@ -275,13 +275,19 @@ private void addCreateSegmentCategory(Context context, PreferenceScreen screen) newSegmentStep.setKey(Settings.SB_CREATE_NEW_SEGMENT_STEP.key); newSegmentStep.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER); newSegmentStep.setOnPreferenceChangeListener((preference1, newValue) -> { - final int newAdjustmentValue = Integer.parseInt(newValue.toString()); - if (newAdjustmentValue == 0) { - Utils.showToastLong(str("revanced_sb_general_adjusting_invalid")); - return false; + try { + final int newAdjustmentValue = Integer.parseInt(newValue.toString()); + if (newAdjustmentValue != 0) { + Settings.SB_CREATE_NEW_SEGMENT_STEP.save(newAdjustmentValue); + return true; + } + } catch (NumberFormatException ex) { + Logger.printInfo(() -> "Invalid new segment step", ex); } - Settings.SB_CREATE_NEW_SEGMENT_STEP.save(newAdjustmentValue); - return true; + + Utils.showToastLong(str("revanced_sb_general_adjusting_invalid")); + updateUI(); + return false; }); Utils.setPreferenceIcon(newSegmentStep, "empty_icon"); category.addPreference(newSegmentStep); @@ -344,8 +350,17 @@ private void addGeneralCategory(final Context context, PreferenceScreen screen) minSegmentDuration.setKey(Settings.SB_SEGMENT_MIN_DURATION.key); minSegmentDuration.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL); minSegmentDuration.setOnPreferenceChangeListener((preference1, newValue) -> { - Settings.SB_SEGMENT_MIN_DURATION.save(Float.valueOf(newValue.toString())); - return true; + try { + Float minTimeDuration = Float.valueOf(newValue.toString()); + Settings.SB_SEGMENT_MIN_DURATION.save(minTimeDuration); + return true; + } catch (NumberFormatException ex) { + Logger.printInfo(() -> "Invalid minimum segment duration", ex); + } + + Utils.showToastLong(str("revanced_sb_general_min_duration_invalid")); + updateUI(); + return false; }); category.addPreference(minSegmentDuration); @@ -359,6 +374,7 @@ private void addGeneralCategory(final Context context, PreferenceScreen screen) Utils.showToastLong(str("revanced_sb_general_uuid_invalid")); return false; } + Settings.SB_PRIVATE_USER_ID.save(newUUID); updateUI(); fetchAndDisplayStats(); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java new file mode 100644 index 0000000000..bb839b8428 --- /dev/null +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/SpoofStreamingDataSideEffectsPreference.java @@ -0,0 +1,86 @@ +package app.revanced.integrations.youtube.settings.preference; + +import static app.revanced.integrations.shared.utils.StringRef.str; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.util.AttributeSet; + +import java.util.Arrays; +import java.util.List; + +import app.revanced.integrations.shared.settings.Setting; +import app.revanced.integrations.shared.utils.Utils; +import app.revanced.integrations.youtube.patches.misc.client.AppClient.ClientType; +import app.revanced.integrations.youtube.settings.Settings; + +@SuppressWarnings({"deprecation", "unused"}) +public class SpoofStreamingDataSideEffectsPreference extends Preference { + + private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { + // Because this listener may run before the ReVanced settings fragment updates Settings, + // this could show the prior config and not the current. + // + // Push this call to the end of the main run queue, + // so all other listeners are done and Settings is up to date. + Utils.runOnMainThread(this::updateUI); + }; + + public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public SpoofStreamingDataSideEffectsPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SpoofStreamingDataSideEffectsPreference(Context context) { + super(context); + } + + private void addChangeListener() { + Setting.preferences.preferences.registerOnSharedPreferenceChangeListener(listener); + } + + private void removeChangeListener() { + Setting.preferences.preferences.unregisterOnSharedPreferenceChangeListener(listener); + } + + @Override + protected void onAttachedToHierarchy(PreferenceManager preferenceManager) { + super.onAttachedToHierarchy(preferenceManager); + updateUI(); + addChangeListener(); + } + + @Override + protected void onPrepareForRemoval() { + super.onPrepareForRemoval(); + removeChangeListener(); + } + + private static final List selectableClientTypes = Arrays.asList( + ClientType.IOS, + ClientType.ANDROID_VR, + ClientType.ANDROID_UNPLUGGED + ); + + private void updateUI() { + final ClientType clientType = Settings.SPOOF_STREAMING_DATA_TYPE.get(); + + final String summaryTextKey; + if (selectableClientTypes.contains(clientType)) { + summaryTextKey = "revanced_spoof_streaming_data_side_effects_" + clientType.name().toLowerCase(); + } else { + summaryTextKey = "revanced_spoof_streaming_data_side_effects_unknown"; + } + + setSummary(str(summaryTextKey)); + } +} \ No newline at end of file diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WatchHistoryStatusPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WatchHistoryStatusPreference.java index 489d2fa2f7..f789f2d73d 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WatchHistoryStatusPreference.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WatchHistoryStatusPreference.java @@ -1,7 +1,6 @@ package app.revanced.integrations.youtube.settings.preference; import static app.revanced.integrations.shared.utils.StringRef.str; -import static app.revanced.integrations.youtube.patches.utils.PatchStatus.SpoofClient; import android.content.Context; import android.content.SharedPreferences; @@ -12,18 +11,17 @@ import app.revanced.integrations.shared.settings.Setting; import app.revanced.integrations.shared.utils.Utils; import app.revanced.integrations.youtube.patches.misc.WatchHistoryPatch.WatchHistoryType; -import app.revanced.integrations.youtube.patches.misc.requests.PlayerRoutes.ClientType; import app.revanced.integrations.youtube.settings.Settings; -@SuppressWarnings("unused") +@SuppressWarnings({"deprecation", "unused"}) public class WatchHistoryStatusPreference extends Preference { private final SharedPreferences.OnSharedPreferenceChangeListener listener = (sharedPreferences, str) -> { - // Because this listener may run before the ReVanced settings fragment updates SettingsEnum, + // Because this listener may run before the ReVanced settings fragment updates Settings, // this could show the prior config and not the current. // // Push this call to the end of the main run queue, - // so all other listeners are done and SettingsEnum is up to date. + // so all other listeners are done and Settings is up to date. Utils.runOnMainThread(this::updateUI); }; @@ -65,14 +63,6 @@ protected void onPrepareForRemoval() { } private void updateUI() { - final ClientType clientTypeIOS = ClientType.IOS; - final boolean spoofClientEnabled = SpoofClient() && Settings.SPOOF_CLIENT.get(); - final boolean containsClientTypeIOS = - Settings.SPOOF_CLIENT_GENERAL.get() == clientTypeIOS || - Settings.SPOOF_CLIENT_LIVESTREAM.get() == clientTypeIOS || - Settings.SPOOF_CLIENT_SHORTS.get() == clientTypeIOS || - Settings.SPOOF_CLIENT_FALLBACK.get() == clientTypeIOS; - final WatchHistoryType watchHistoryType = Settings.WATCH_HISTORY_TYPE.get(); final boolean blockWatchHistory = watchHistoryType == WatchHistoryType.BLOCK; final boolean replaceWatchHistory = watchHistoryType == WatchHistoryType.REPLACE; @@ -80,14 +70,10 @@ private void updateUI() { final String summaryTextKey; if (blockWatchHistory) { summaryTextKey = "revanced_watch_history_about_status_blocked"; - } else if (spoofClientEnabled && containsClientTypeIOS) { - summaryTextKey = replaceWatchHistory - ? "revanced_watch_history_about_status_ios_replaced" - : "revanced_watch_history_about_status_ios_original"; + } else if (replaceWatchHistory) { + summaryTextKey = "revanced_watch_history_about_status_replaced"; } else { - summaryTextKey = replaceWatchHistory - ? "revanced_watch_history_about_status_android_replaced" - : "revanced_watch_history_about_status_android_original"; + summaryTextKey = "revanced_watch_history_about_status_original"; } setSummary(str(summaryTextKey)); diff --git a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WhitelistedChannelsPreference.java b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WhitelistedChannelsPreference.java index c7ea612c6c..a843727cca 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WhitelistedChannelsPreference.java +++ b/app/src/main/java/app/revanced/integrations/youtube/settings/preference/WhitelistedChannelsPreference.java @@ -22,9 +22,7 @@ import app.revanced.integrations.youtube.whitelist.Whitelist; import app.revanced.integrations.youtube.whitelist.Whitelist.WhitelistType; -/** - * @noinspection ALL - */ +@SuppressWarnings({"unused", "deprecation"}) public class WhitelistedChannelsPreference extends Preference implements Preference.OnPreferenceClickListener { private static final WhitelistType whitelistTypePlaybackSpeed = WhitelistType.PLAYBACK_SPEED; @@ -38,7 +36,7 @@ public class WhitelistedChannelsPreference extends Preference implements Prefere final int entrySize = BooleanUtils.toInteger(playbackSpeedIncluded) + BooleanUtils.toInteger(sponsorBlockIncluded); - if (entrySize != 0 && mEntries == null && mEntryValues == null) { + if (entrySize != 0) { mEntries = new String[entrySize]; mEntryValues = new WhitelistType[entrySize]; @@ -112,16 +110,14 @@ private static void showWhitelistedChannelDialog(Context context, WhitelistType entriesContainer.setOrientation(LinearLayout.VERTICAL); for (final VideoChannel entry : mEntries) { String author = entry.getChannelName(); - View entryView = getEntryView(context, author, v -> { - new AlertDialog.Builder(context) - .setMessage(str("revanced_whitelist_remove_dialog_message", author, whitelistType.getFriendlyName())) - .setPositiveButton(android.R.string.ok, (dialog, which) -> { - Whitelist.removeFromWhitelist(whitelistType, entry.getChannelId()); - entriesContainer.removeView(entriesContainer.findViewWithTag(author)); - }) - .setNegativeButton(android.R.string.cancel, null) - .show(); - }); + View entryView = getEntryView(context, author, v -> new AlertDialog.Builder(context) + .setMessage(str("revanced_whitelist_remove_dialog_message", author, whitelistType.getFriendlyName())) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + Whitelist.removeFromWhitelist(whitelistType, entry.getChannelId()); + entriesContainer.removeView(entriesContainer.findViewWithTag(author)); + }) + .setNegativeButton(android.R.string.cancel, null) + .show()); entryView.setTag(author); entriesContainer.addView(entryView); } diff --git a/app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java b/app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java index a9f7f09826..958c204d32 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java +++ b/app/src/main/java/app/revanced/integrations/youtube/shared/NavigationBar.java @@ -238,18 +238,23 @@ private static void navigationTabCreatedCallback(NavigationButton button, View t public enum NavigationButton { HOME("PIVOT_HOME"), + HOME_CAIRO("TAB_HOME_CAIRO"), SHORTS("TAB_SHORTS"), + SHORTS_CAIRO("TAB_SHORTS_CAIRO"), /** * Create new video tab. * This tab will never be in a selected state, even if the create video UI is on screen. */ CREATE("CREATION_TAB_LARGE"), + CREATE_CAIRO("CREATION_TAB_LARGE_CAIRO"), SUBSCRIPTIONS("PIVOT_SUBSCRIPTIONS"), + SUBSCRIPTIONS_CAIRO("TAB_SUBSCRIPTIONS_CAIRO"), /** * Notifications tab. Only present when * {@link Settings#SWITCH_CREATE_WITH_NOTIFICATIONS_BUTTON} is active. */ NOTIFICATIONS("TAB_ACTIVITY"), + NOTIFICATIONS_CAIRO("TAB_ACTIVITY_CAIRO"), /** * Library tab when the user is not logged in. */ @@ -309,6 +314,22 @@ public static NavigationButton getSelectedNavigationButton() { this.ytEnumName = ytEnumName; } + public boolean isHomeTab() { + return this == HOME || this == HOME_CAIRO; + } + + public boolean isShortsTab() { + return this == SHORTS || this == SHORTS_CAIRO; + } + + public boolean isSubscriptionsTab() { + return this == SUBSCRIPTIONS || this == SUBSCRIPTIONS_CAIRO; + } + + public boolean isNotificationTab() { + return this == NOTIFICATIONS || this == NOTIFICATIONS_CAIRO; + } + public boolean isLibraryOrYouTab() { return this == LIBRARY_YOU || this == LIBRARY_PIVOT_UNKNOWN || this == LIBRARY_OLD_UI || this == LIBRARY_INCOGNITO diff --git a/app/src/main/java/app/revanced/integrations/youtube/utils/ExtendedUtils.java b/app/src/main/java/app/revanced/integrations/youtube/utils/ExtendedUtils.java index a8db2557c6..477cc36ad9 100644 --- a/app/src/main/java/app/revanced/integrations/youtube/utils/ExtendedUtils.java +++ b/app/src/main/java/app/revanced/integrations/youtube/utils/ExtendedUtils.java @@ -67,11 +67,11 @@ public static void setCommentPreviewSettings() { } private static final Setting[] additionalSettings = { - Settings.HIDE_PLAYER_FLYOUT_MENU_AMBIENT, Settings.HIDE_PLAYER_FLYOUT_MENU_HELP, Settings.HIDE_PLAYER_FLYOUT_MENU_LOOP, Settings.HIDE_PLAYER_FLYOUT_MENU_PIP, Settings.HIDE_PLAYER_FLYOUT_MENU_PREMIUM_CONTROLS, + Settings.HIDE_PLAYER_FLYOUT_MENU_SLEEP_TIMER, Settings.HIDE_PLAYER_FLYOUT_MENU_STABLE_VOLUME, Settings.HIDE_PLAYER_FLYOUT_MENU_STATS_FOR_NERDS, Settings.HIDE_PLAYER_FLYOUT_MENU_WATCH_IN_VR, @@ -99,11 +99,11 @@ private static boolean isAdditionalSettingsEnabled() { boolean additionalSettingsEnabled = true; final BooleanSetting[] additionalSettings = { - Settings.HIDE_PLAYER_FLYOUT_MENU_AMBIENT, Settings.HIDE_PLAYER_FLYOUT_MENU_HELP, Settings.HIDE_PLAYER_FLYOUT_MENU_LOOP, Settings.HIDE_PLAYER_FLYOUT_MENU_PIP, Settings.HIDE_PLAYER_FLYOUT_MENU_PREMIUM_CONTROLS, + Settings.HIDE_PLAYER_FLYOUT_MENU_SLEEP_TIMER, Settings.HIDE_PLAYER_FLYOUT_MENU_STABLE_VOLUME, Settings.HIDE_PLAYER_FLYOUT_MENU_STATS_FOR_NERDS, Settings.HIDE_PLAYER_FLYOUT_MENU_WATCH_IN_VR, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d24f0f51c2..d516bdb986 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] # noinspection GradleDependency agp = "8.0.2" -annotation = "1.8.1" -lang3 = "3.15.0" +annotation = "1.8.2" +lang3 = "3.17.0" # noinspection GradleDependency kotlin = "1.7.21" diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 2c3521197d..a4b76b9530 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 68e8816d71..2b189974c2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionSha256Sum=5b9c5eb3f9fc2c94abaea57d90bd78747ca117ddbbf96c859d3741181a12bf2a +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/package-lock.json b/package-lock.json index 6d31bf1347..2911c6a81e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,8 @@ "@saithodev/semantic-release-backmerge": "^4.0.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", - "gradle-semantic-release-plugin": "^1.9.1", - "semantic-release": "^23.0.0" + "gradle-semantic-release-plugin": "^1.9.2", + "semantic-release": "^24.1.0" } }, "node_modules/@babel/code-frame": { @@ -757,6 +757,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, "node_modules/@semantic-release/changelog": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@semantic-release/changelog/-/changelog-6.0.3.tgz", @@ -1212,6 +1218,12 @@ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.8", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", + "dev": true + }, "node_modules/agent-base": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", @@ -1282,6 +1294,12 @@ "integrity": "sha512-QXu7BPrP29VllRxH8GwB7x5iX5qWKAAMLqKQGWTeLWVlNHNOpVMJ91dsxQAIWXpjuW5wqvxu3Jd/nRjrJ+0pqg==", "dev": true }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1376,10 +1394,85 @@ "node": ">=6" } }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", "dev": true, "dependencies": { "string-width": "^4.2.0" @@ -1502,6 +1595,18 @@ "node": ">=16" } }, + "node_modules/convert-hrtime": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-5.0.0.tgz", + "integrity": "sha512-lOETlkIeYSJWcbbcvjRKGxVMXJR+8+OQb/mTPbA4ObPMytYIsUbuOE0Jzy60hjARYszq1id0j8KgVhC+WGZVTg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -1808,6 +1913,18 @@ "node": ">=6" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -1897,9 +2014,9 @@ } }, "node_modules/figures": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.0.1.tgz", - "integrity": "sha512-0oY/olScYD4IhQ8u//gCPA4F3mlTn2dacYmiDm/mbDQvpmLjV4uH+zhsQ5IyXRyvqkvtUkXkNdGvg5OFJTCsuQ==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "dependencies": { "is-unicode-supported": "^2.0.0" @@ -1995,6 +2112,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/function-timeout": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/function-timeout/-/function-timeout-1.0.2.tgz", + "integrity": "sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2090,9 +2219,9 @@ "dev": true }, "node_modules/gradle-semantic-release-plugin": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.9.1.tgz", - "integrity": "sha512-lCrw22itszP/FLSL3N61E40vH1+CU95/4LG9ZF+Fxr8tcx7EPthh2eqVPAq67udFlM8ZgO2LETnn8LSDRq1J2w==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/gradle-semantic-release-plugin/-/gradle-semantic-release-plugin-1.9.2.tgz", + "integrity": "sha512-8qpf4GYFPQ+UMUymYBy/VchOOwLILAWzZMrZX1R0RR3JMgJBMN2R0tJn92R/3rXmxx4OAqwUFH6Np51eFoxr3w==", "dev": true, "funding": [ { @@ -2108,7 +2237,7 @@ "node": ">=18" }, "peerDependencies": { - "semantic-release": "^23.0.0" + "semantic-release": "^24.0.0" } }, "node_modules/handlebars": { @@ -2153,6 +2282,15 @@ "node": ">= 0.4" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/hook-std": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hook-std/-/hook-std-3.0.0.tgz", @@ -2384,6 +2522,18 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -2633,6 +2783,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.1.tgz", "integrity": "sha512-EgxRjgK9axsQuUa/oKMx5DEY8oXpKJfk61rT5iY3aRlgU6QJtUcxU5OAymdhCvWvhYcd9FKmO5eQoX8m9VGJXg==", "dev": true, + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -2739,6 +2890,17 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -2794,9 +2956,9 @@ } }, "node_modules/npm": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/npm/-/npm-10.4.0.tgz", - "integrity": "sha512-RS7Mx0OVfXlOcQLRePuDIYdFCVBPCNapWHplDK+mh7GDdP/Tvor4ocuybRRPSvfcRb2vjRJt1fHCqw3cr8qACQ==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.8.2.tgz", + "integrity": "sha512-x/AIjFIKRllrhcb48dqUNAAZl0ig9+qMuN91RpZo3Cb2+zuibfh+KISl6+kVVyktDz230JKc208UkQwwMqyB+w==", "bundleDependencies": [ "@isaacs/string-locale-compare", "@npmcli/arborist", @@ -2805,6 +2967,7 @@ "@npmcli/map-workspaces", "@npmcli/package-json", "@npmcli/promise-spawn", + "@npmcli/redact", "@npmcli/run-script", "@sigstore/tuf", "abbrev", @@ -2813,8 +2976,6 @@ "chalk", "ci-info", "cli-columns", - "cli-table3", - "columnify", "fastest-levenshtein", "fs-minipass", "glob", @@ -2850,7 +3011,6 @@ "npm-profile", "npm-registry-fetch", "npm-user-validate", - "npmlog", "p-map", "pacote", "parse-conflict-json", @@ -2872,73 +3032,71 @@ "dev": true, "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/arborist": "^7.2.1", - "@npmcli/config": "^8.0.2", - "@npmcli/fs": "^3.1.0", - "@npmcli/map-workspaces": "^3.0.4", - "@npmcli/package-json": "^5.0.0", - "@npmcli/promise-spawn": "^7.0.1", - "@npmcli/run-script": "^7.0.4", - "@sigstore/tuf": "^2.3.0", + "@npmcli/arborist": "^7.5.4", + "@npmcli/config": "^8.3.4", + "@npmcli/fs": "^3.1.1", + "@npmcli/map-workspaces": "^3.0.6", + "@npmcli/package-json": "^5.2.0", + "@npmcli/promise-spawn": "^7.0.2", + "@npmcli/redact": "^2.0.1", + "@npmcli/run-script": "^8.1.0", + "@sigstore/tuf": "^2.3.4", "abbrev": "^2.0.0", "archy": "~1.0.0", - "cacache": "^18.0.2", + "cacache": "^18.0.3", "chalk": "^5.3.0", "ci-info": "^4.0.0", "cli-columns": "^4.0.0", - "cli-table3": "^0.6.3", - "columnify": "^1.6.0", "fastest-levenshtein": "^1.0.16", "fs-minipass": "^3.0.3", - "glob": "^10.3.10", + "glob": "^10.4.2", "graceful-fs": "^4.2.11", - "hosted-git-info": "^7.0.1", - "ini": "^4.1.1", - "init-package-json": "^6.0.0", - "is-cidr": "^5.0.3", - "json-parse-even-better-errors": "^3.0.1", - "libnpmaccess": "^8.0.1", - "libnpmdiff": "^6.0.3", - "libnpmexec": "^7.0.4", - "libnpmfund": "^5.0.1", - "libnpmhook": "^10.0.0", - "libnpmorg": "^6.0.1", - "libnpmpack": "^6.0.3", - "libnpmpublish": "^9.0.2", - "libnpmsearch": "^7.0.0", - "libnpmteam": "^6.0.0", - "libnpmversion": "^5.0.1", - "make-fetch-happen": "^13.0.0", - "minimatch": "^9.0.3", - "minipass": "^7.0.4", + "hosted-git-info": "^7.0.2", + "ini": "^4.1.3", + "init-package-json": "^6.0.3", + "is-cidr": "^5.1.0", + "json-parse-even-better-errors": "^3.0.2", + "libnpmaccess": "^8.0.6", + "libnpmdiff": "^6.1.4", + "libnpmexec": "^8.1.3", + "libnpmfund": "^5.0.12", + "libnpmhook": "^10.0.5", + "libnpmorg": "^6.0.6", + "libnpmpack": "^7.0.4", + "libnpmpublish": "^9.0.9", + "libnpmsearch": "^7.0.6", + "libnpmteam": "^6.0.5", + "libnpmversion": "^6.0.3", + "make-fetch-happen": "^13.0.1", + "minimatch": "^9.0.5", + "minipass": "^7.1.1", "minipass-pipeline": "^1.2.4", "ms": "^2.1.2", - "node-gyp": "^10.0.1", - "nopt": "^7.2.0", - "normalize-package-data": "^6.0.0", + "node-gyp": "^10.1.0", + "nopt": "^7.2.1", + "normalize-package-data": "^6.0.2", "npm-audit-report": "^5.0.0", "npm-install-checks": "^6.3.0", - "npm-package-arg": "^11.0.1", - "npm-pick-manifest": "^9.0.0", - "npm-profile": "^9.0.0", - "npm-registry-fetch": "^16.1.0", - "npm-user-validate": "^2.0.0", - "npmlog": "^7.0.1", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.1.0", + "npm-profile": "^10.0.0", + "npm-registry-fetch": "^17.1.0", + "npm-user-validate": "^2.0.1", "p-map": "^4.0.0", - "pacote": "^17.0.6", + "pacote": "^18.0.6", "parse-conflict-json": "^3.0.1", - "proc-log": "^3.0.0", + "proc-log": "^4.2.0", "qrcode-terminal": "^0.12.0", - "read": "^2.1.0", - "semver": "^7.5.4", - "spdx-expression-parse": "^3.0.1", - "ssri": "^10.0.5", + "read": "^3.0.1", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "ssri": "^10.0.6", "supports-color": "^9.4.0", - "tar": "^6.2.0", + "tar": "^6.2.1", "text-table": "~0.2.0", "tiny-relative-date": "^1.3.0", "treeverse": "^3.0.0", - "validate-npm-package-name": "^5.0.0", + "validate-npm-package-name": "^5.0.1", "which": "^4.0.0", "write-file-atomic": "^5.0.1" }, @@ -2962,16 +3120,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/@colors/colors": { - "version": "1.5.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, "node_modules/npm/node_modules/@isaacs/cliui": { "version": "8.0.2", "dev": true, @@ -3046,7 +3194,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/@npmcli/agent": { - "version": "2.2.0", + "version": "2.2.2", "dev": true, "inBundle": true, "license": "ISC", @@ -3055,49 +3203,51 @@ "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.1", "lru-cache": "^10.0.1", - "socks-proxy-agent": "^8.0.1" + "socks-proxy-agent": "^8.0.3" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/arborist": { - "version": "7.3.1", + "version": "7.5.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^3.1.0", - "@npmcli/installed-package-contents": "^2.0.2", + "@npmcli/fs": "^3.1.1", + "@npmcli/installed-package-contents": "^2.1.0", "@npmcli/map-workspaces": "^3.0.2", - "@npmcli/metavuln-calculator": "^7.0.0", + "@npmcli/metavuln-calculator": "^7.1.1", "@npmcli/name-from-folder": "^2.0.0", "@npmcli/node-gyp": "^3.0.0", - "@npmcli/package-json": "^5.0.0", - "@npmcli/query": "^3.0.1", - "@npmcli/run-script": "^7.0.2", - "bin-links": "^4.0.1", - "cacache": "^18.0.0", + "@npmcli/package-json": "^5.1.0", + "@npmcli/query": "^3.1.0", + "@npmcli/redact": "^2.0.0", + "@npmcli/run-script": "^8.1.0", + "bin-links": "^4.0.4", + "cacache": "^18.0.3", "common-ancestor-path": "^1.0.1", - "hosted-git-info": "^7.0.1", - "json-parse-even-better-errors": "^3.0.0", + "hosted-git-info": "^7.0.2", + "json-parse-even-better-errors": "^3.0.2", "json-stringify-nice": "^1.1.4", - "minimatch": "^9.0.0", - "nopt": "^7.0.0", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^7.2.1", "npm-install-checks": "^6.2.0", - "npm-package-arg": "^11.0.1", - "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", + "npm-package-arg": "^11.0.2", + "npm-pick-manifest": "^9.0.1", + "npm-registry-fetch": "^17.0.1", + "pacote": "^18.0.6", "parse-conflict-json": "^3.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.2.0", + "proggy": "^2.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", - "ssri": "^10.0.5", + "ssri": "^10.0.6", "treeverse": "^3.0.0", "walk-up-path": "^3.0.1" }, @@ -3109,17 +3259,17 @@ } }, "node_modules/npm/node_modules/@npmcli/config": { - "version": "8.1.0", + "version": "8.3.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/map-workspaces": "^3.0.2", + "@npmcli/package-json": "^5.1.1", "ci-info": "^4.0.0", - "ini": "^4.1.0", - "nopt": "^7.0.0", - "proc-log": "^3.0.0", - "read-package-json-fast": "^3.0.2", + "ini": "^4.1.2", + "nopt": "^7.2.1", + "proc-log": "^4.2.0", "semver": "^7.3.5", "walk-up-path": "^3.0.1" }, @@ -3127,35 +3277,8 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/@npmcli/disparity-colors": { - "version": "3.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "ansi-styles": "^4.3.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/npm/node_modules/@npmcli/disparity-colors/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/npm/node_modules/@npmcli/fs": { - "version": "3.1.0", + "version": "3.1.1", "dev": true, "inBundle": true, "license": "ISC", @@ -3167,15 +3290,16 @@ } }, "node_modules/npm/node_modules/@npmcli/git": { - "version": "5.0.4", + "version": "5.0.8", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/promise-spawn": "^7.0.0", + "ini": "^4.1.3", "lru-cache": "^10.0.1", "npm-pick-manifest": "^9.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.0.0", "promise-inflight": "^1.0.1", "promise-retry": "^2.0.1", "semver": "^7.3.5", @@ -3186,7 +3310,7 @@ } }, "node_modules/npm/node_modules/@npmcli/installed-package-contents": { - "version": "2.0.2", + "version": "2.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3195,14 +3319,14 @@ "npm-normalize-package-bin": "^3.0.0" }, "bin": { - "installed-package-contents": "lib/index.js" + "installed-package-contents": "bin/index.js" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@npmcli/map-workspaces": { - "version": "3.0.4", + "version": "3.0.6", "dev": true, "inBundle": true, "license": "ISC", @@ -3217,14 +3341,15 @@ } }, "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { - "version": "7.0.0", + "version": "7.1.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "cacache": "^18.0.0", "json-parse-even-better-errors": "^3.0.0", - "pacote": "^17.0.0", + "pacote": "^18.0.0", + "proc-log": "^4.1.0", "semver": "^7.3.5" }, "engines": { @@ -3250,7 +3375,7 @@ } }, "node_modules/npm/node_modules/@npmcli/package-json": { - "version": "5.0.0", + "version": "5.2.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3260,7 +3385,7 @@ "hosted-git-info": "^7.0.0", "json-parse-even-better-errors": "^3.0.0", "normalize-package-data": "^6.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.0.0", "semver": "^7.5.3" }, "engines": { @@ -3268,7 +3393,7 @@ } }, "node_modules/npm/node_modules/@npmcli/promise-spawn": { - "version": "7.0.1", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -3280,7 +3405,7 @@ } }, "node_modules/npm/node_modules/@npmcli/query": { - "version": "3.0.1", + "version": "3.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3291,8 +3416,17 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "2.0.1", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, "node_modules/npm/node_modules/@npmcli/run-script": { - "version": "7.0.4", + "version": "8.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -3301,6 +3435,7 @@ "@npmcli/package-json": "^5.0.0", "@npmcli/promise-spawn": "^7.0.0", "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", "which": "^4.0.0" }, "engines": { @@ -3318,19 +3453,19 @@ } }, "node_modules/npm/node_modules/@sigstore/bundle": { - "version": "2.1.1", + "version": "2.3.2", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/core": { - "version": "0.2.0", + "version": "1.1.0", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -3339,51 +3474,53 @@ } }, "node_modules/npm/node_modules/@sigstore/protobuf-specs": { - "version": "0.2.1", + "version": "0.3.2", "dev": true, "inBundle": true, "license": "Apache-2.0", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/sign": { - "version": "2.2.1", + "version": "2.3.2", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1", - "make-fetch-happen": "^13.0.0" + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/tuf": { - "version": "2.3.0", + "version": "2.3.4", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/protobuf-specs": "^0.2.1", - "tuf-js": "^2.2.0" + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/@sigstore/verify": { - "version": "0.1.0", + "version": "1.2.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1" + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -3399,13 +3536,13 @@ } }, "node_modules/npm/node_modules/@tufjs/models": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^9.0.3" + "minimatch": "^9.0.4" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -3421,7 +3558,7 @@ } }, "node_modules/npm/node_modules/agent-base": { - "version": "7.1.0", + "version": "7.1.1", "dev": true, "inBundle": true, "license": "MIT", @@ -3478,15 +3615,6 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/are-we-there-yet": { - "version": "4.0.2", - "dev": true, - "inBundle": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", "dev": true, @@ -3494,7 +3622,7 @@ "license": "MIT" }, "node_modules/npm/node_modules/bin-links": { - "version": "4.0.3", + "version": "4.0.4", "dev": true, "inBundle": true, "license": "ISC", @@ -3509,12 +3637,15 @@ } }, "node_modules/npm/node_modules/binary-extensions": { - "version": "2.2.0", + "version": "2.3.0", "dev": true, "inBundle": true, "license": "MIT", "engines": { "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/npm/node_modules/brace-expansion": { @@ -3526,17 +3657,8 @@ "balanced-match": "^1.0.0" } }, - "node_modules/npm/node_modules/builtins": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "semver": "^7.0.0" - } - }, "node_modules/npm/node_modules/cacache": { - "version": "18.0.2", + "version": "18.0.3", "dev": true, "inBundle": true, "license": "ISC", @@ -3595,7 +3717,7 @@ } }, "node_modules/npm/node_modules/cidr-regex": { - "version": "4.0.3", + "version": "4.1.1", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -3628,32 +3750,8 @@ "node": ">= 10" } }, - "node_modules/npm/node_modules/cli-table3": { - "version": "0.6.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/npm/node_modules/clone": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/npm/node_modules/cmd-shim": { - "version": "6.0.2", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", @@ -3679,52 +3777,24 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/color-support": { - "version": "1.1.3", + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", "dev": true, "inBundle": true, - "license": "ISC", - "bin": { - "color-support": "bin.js" - } + "license": "ISC" }, - "node_modules/npm/node_modules/columnify": { - "version": "1.6.0", + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.3", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "strip-ansi": "^6.0.1", - "wcwidth": "^1.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/npm/node_modules/common-ancestor-path": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/console-control-strings": { - "version": "1.1.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "node": ">= 8" } }, "node_modules/npm/node_modules/cross-spawn/node_modules/which": { @@ -3755,7 +3825,7 @@ } }, "node_modules/npm/node_modules/debug": { - "version": "4.3.4", + "version": "4.3.5", "dev": true, "inBundle": true, "license": "MIT", @@ -3777,20 +3847,8 @@ "inBundle": true, "license": "MIT" }, - "node_modules/npm/node_modules/defaults": { - "version": "1.0.4", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/npm/node_modules/diff": { - "version": "5.1.0", + "version": "5.2.0", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -3851,7 +3909,7 @@ } }, "node_modules/npm/node_modules/foreground-child": { - "version": "3.1.1", + "version": "3.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -3878,51 +3936,24 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/function-bind": { - "version": "1.1.2", - "dev": true, - "inBundle": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/npm/node_modules/gauge": { - "version": "5.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^4.0.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/glob": { - "version": "10.3.10", + "version": "10.4.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.5", - "minimatch": "^9.0.1", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", - "path-scurry": "^1.10.1" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3934,26 +3965,8 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/has-unicode": { - "version": "2.0.1", - "dev": true, - "inBundle": true, - "license": "ISC" - }, - "node_modules/npm/node_modules/hasown": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/npm/node_modules/hosted-git-info": { - "version": "7.0.1", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "ISC", @@ -3971,7 +3984,7 @@ "license": "BSD-2-Clause" }, "node_modules/npm/node_modules/http-proxy-agent": { - "version": "7.0.0", + "version": "7.0.2", "dev": true, "inBundle": true, "license": "MIT", @@ -3984,7 +3997,7 @@ } }, "node_modules/npm/node_modules/https-proxy-agent": { - "version": "7.0.2", + "version": "7.0.5", "dev": true, "inBundle": true, "license": "MIT", @@ -4010,7 +4023,7 @@ } }, "node_modules/npm/node_modules/ignore-walk": { - "version": "6.0.4", + "version": "6.0.5", "dev": true, "inBundle": true, "license": "ISC", @@ -4040,7 +4053,7 @@ } }, "node_modules/npm/node_modules/ini": { - "version": "4.1.1", + "version": "4.1.3", "dev": true, "inBundle": true, "license": "ISC", @@ -4049,15 +4062,15 @@ } }, "node_modules/npm/node_modules/init-package-json": { - "version": "6.0.0", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@npmcli/package-json": "^5.0.0", "npm-package-arg": "^11.0.0", "promzard": "^1.0.0", - "read": "^2.0.0", - "read-package-json": "^7.0.0", + "read": "^3.0.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4", "validate-npm-package-name": "^5.0.0" @@ -4066,11 +4079,18 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/ip": { - "version": "2.0.0", + "node_modules/npm/node_modules/ip-address": { + "version": "9.0.5", "dev": true, "inBundle": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } }, "node_modules/npm/node_modules/ip-regex": { "version": "5.0.0", @@ -4085,29 +4105,17 @@ } }, "node_modules/npm/node_modules/is-cidr": { - "version": "5.0.3", + "version": "5.1.0", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { - "cidr-regex": "4.0.3" + "cidr-regex": "^4.1.1" }, "engines": { "node": ">=14" } }, - "node_modules/npm/node_modules/is-core-module": { - "version": "2.13.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "dev": true, @@ -4130,7 +4138,7 @@ "license": "ISC" }, "node_modules/npm/node_modules/jackspeak": { - "version": "2.3.6", + "version": "3.4.0", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", @@ -4147,8 +4155,14 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/npm/node_modules/jsbn": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT" + }, "node_modules/npm/node_modules/json-parse-even-better-errors": { - "version": "3.0.1", + "version": "3.0.2", "dev": true, "inBundle": true, "license": "MIT", @@ -4187,52 +4201,50 @@ "license": "MIT" }, "node_modules/npm/node_modules/libnpmaccess": { - "version": "8.0.2", + "version": "8.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-package-arg": "^11.0.1", - "npm-registry-fetch": "^16.0.0" + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmdiff": { - "version": "6.0.6", + "version": "6.1.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/disparity-colors": "^3.0.0", - "@npmcli/installed-package-contents": "^2.0.2", - "binary-extensions": "^2.2.0", + "@npmcli/arborist": "^7.5.4", + "@npmcli/installed-package-contents": "^2.1.0", + "binary-extensions": "^2.3.0", "diff": "^5.1.0", - "minimatch": "^9.0.0", - "npm-package-arg": "^11.0.1", - "pacote": "^17.0.4", - "tar": "^6.2.0" + "minimatch": "^9.0.4", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "tar": "^6.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmexec": { - "version": "7.0.7", + "version": "8.1.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/run-script": "^7.0.2", + "@npmcli/arborist": "^7.5.4", + "@npmcli/run-script": "^8.1.0", "ci-info": "^4.0.0", - "npm-package-arg": "^11.0.1", - "npmlog": "^7.0.1", - "pacote": "^17.0.4", - "proc-log": "^3.0.0", - "read": "^2.0.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6", + "proc-log": "^4.2.0", + "read": "^3.0.1", "read-package-json-fast": "^3.0.2", "semver": "^7.3.7", "walk-up-path": "^3.0.1" @@ -4242,112 +4254,112 @@ } }, "node_modules/npm/node_modules/libnpmfund": { - "version": "5.0.4", + "version": "5.0.12", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1" + "@npmcli/arborist": "^7.5.4" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmhook": { - "version": "10.0.1", + "version": "10.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmorg": { - "version": "6.0.2", + "version": "6.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmpack": { - "version": "6.0.6", + "version": "7.0.4", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/arborist": "^7.2.1", - "@npmcli/run-script": "^7.0.2", - "npm-package-arg": "^11.0.1", - "pacote": "^17.0.4" + "@npmcli/arborist": "^7.5.4", + "@npmcli/run-script": "^8.1.0", + "npm-package-arg": "^11.0.2", + "pacote": "^18.0.6" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmpublish": { - "version": "9.0.4", + "version": "9.0.9", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "ci-info": "^4.0.0", - "normalize-package-data": "^6.0.0", - "npm-package-arg": "^11.0.1", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", + "normalize-package-data": "^6.0.1", + "npm-package-arg": "^11.0.2", + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.2.0", "semver": "^7.3.7", "sigstore": "^2.2.0", - "ssri": "^10.0.5" + "ssri": "^10.0.6" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmsearch": { - "version": "7.0.1", + "version": "7.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmteam": { - "version": "6.0.1", + "version": "6.0.5", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "aproba": "^2.0.0", - "npm-registry-fetch": "^16.0.0" + "npm-registry-fetch": "^17.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/libnpmversion": { - "version": "5.0.2", + "version": "6.0.3", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "@npmcli/git": "^5.0.3", - "@npmcli/run-script": "^7.0.2", - "json-parse-even-better-errors": "^3.0.0", - "proc-log": "^3.0.0", + "@npmcli/git": "^5.0.7", + "@npmcli/run-script": "^8.1.0", + "json-parse-even-better-errors": "^3.0.2", + "proc-log": "^4.2.0", "semver": "^7.3.7" }, "engines": { @@ -4355,7 +4367,7 @@ } }, "node_modules/npm/node_modules/lru-cache": { - "version": "10.1.0", + "version": "10.2.2", "dev": true, "inBundle": true, "license": "ISC", @@ -4364,7 +4376,7 @@ } }, "node_modules/npm/node_modules/make-fetch-happen": { - "version": "13.0.0", + "version": "13.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -4378,6 +4390,7 @@ "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.3", + "proc-log": "^4.2.0", "promise-retry": "^2.0.1", "ssri": "^10.0.0" }, @@ -4386,7 +4399,7 @@ } }, "node_modules/npm/node_modules/minimatch": { - "version": "9.0.3", + "version": "9.0.5", "dev": true, "inBundle": true, "license": "ISC", @@ -4401,7 +4414,7 @@ } }, "node_modules/npm/node_modules/minipass": { - "version": "7.0.4", + "version": "7.1.2", "dev": true, "inBundle": true, "license": "ISC", @@ -4422,7 +4435,7 @@ } }, "node_modules/npm/node_modules/minipass-fetch": { - "version": "3.0.4", + "version": "3.0.5", "dev": true, "inBundle": true, "license": "MIT", @@ -4462,28 +4475,6 @@ "node": ">=8" } }, - "node_modules/npm/node_modules/minipass-json-stream": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "jsonparse": "^1.3.1", - "minipass": "^3.0.0" - } - }, - "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { - "version": "3.3.6", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/npm/node_modules/minipass-pipeline": { "version": "1.2.4", "dev": true, @@ -4594,7 +4585,7 @@ } }, "node_modules/npm/node_modules/node-gyp": { - "version": "10.0.1", + "version": "10.1.0", "dev": true, "inBundle": true, "license": "MIT", @@ -4617,8 +4608,17 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/npm/node_modules/node-gyp/node_modules/proc-log": { + "version": "3.0.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/npm/node_modules/nopt": { - "version": "7.2.0", + "version": "7.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -4633,13 +4633,12 @@ } }, "node_modules/npm/node_modules/normalize-package-data": { - "version": "6.0.0", + "version": "6.0.2", "dev": true, "inBundle": true, "license": "BSD-2-Clause", "dependencies": { "hosted-git-info": "^7.0.0", - "is-core-module": "^2.8.1", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, @@ -4657,7 +4656,7 @@ } }, "node_modules/npm/node_modules/npm-bundled": { - "version": "3.0.0", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", @@ -4690,13 +4689,13 @@ } }, "node_modules/npm/node_modules/npm-package-arg": { - "version": "11.0.1", + "version": "11.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "hosted-git-info": "^7.0.0", - "proc-log": "^3.0.0", + "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" }, @@ -4717,7 +4716,7 @@ } }, "node_modules/npm/node_modules/npm-pick-manifest": { - "version": "9.0.0", + "version": "9.1.0", "dev": true, "inBundle": true, "license": "ISC", @@ -4732,38 +4731,39 @@ } }, "node_modules/npm/node_modules/npm-profile": { - "version": "9.0.0", + "version": "10.0.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0" + "npm-registry-fetch": "^17.0.1", + "proc-log": "^4.0.0" }, "engines": { - "node": "^16.14.0 || >=18.0.0" + "node": ">=18.0.0" } }, "node_modules/npm/node_modules/npm-registry-fetch": { - "version": "16.1.0", + "version": "17.1.0", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { + "@npmcli/redact": "^2.0.0", + "jsonparse": "^1.3.1", "make-fetch-happen": "^13.0.0", "minipass": "^7.0.2", "minipass-fetch": "^3.0.0", - "minipass-json-stream": "^1.0.1", "minizlib": "^2.1.2", "npm-package-arg": "^11.0.0", - "proc-log": "^3.0.0" + "proc-log": "^4.0.0" }, "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/npm/node_modules/npm-user-validate": { - "version": "2.0.0", + "version": "2.0.1", "dev": true, "inBundle": true, "license": "BSD-2-Clause", @@ -4771,21 +4771,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/npmlog": { - "version": "7.0.1", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "are-we-there-yet": "^4.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^5.0.0", - "set-blocking": "^2.0.0" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", "dev": true, @@ -4801,33 +4786,38 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.0", + "dev": true, + "inBundle": true, + "license": "BlueOak-1.0.0" + }, "node_modules/npm/node_modules/pacote": { - "version": "17.0.6", + "version": "18.0.6", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", "@npmcli/promise-spawn": "^7.0.0", - "@npmcli/run-script": "^7.0.0", + "@npmcli/run-script": "^8.0.0", "cacache": "^18.0.0", "fs-minipass": "^3.0.0", "minipass": "^7.0.2", "npm-package-arg": "^11.0.0", "npm-packlist": "^8.0.0", "npm-pick-manifest": "^9.0.0", - "npm-registry-fetch": "^16.0.0", - "proc-log": "^3.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", "promise-retry": "^2.0.1", - "read-package-json": "^7.0.0", - "read-package-json-fast": "^3.0.0", "sigstore": "^2.2.0", "ssri": "^10.0.0", "tar": "^6.1.11" }, "bin": { - "pacote": "lib/bin.js" + "pacote": "bin/index.js" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -4857,23 +4847,23 @@ } }, "node_modules/npm/node_modules/path-scurry": { - "version": "1.10.1", + "version": "1.11.1", "dev": true, "inBundle": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^9.1.1 || ^10.0.0", + "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/npm/node_modules/postcss-selector-parser": { - "version": "6.0.15", + "version": "6.1.0", "dev": true, "inBundle": true, "license": "MIT", @@ -4886,7 +4876,16 @@ } }, "node_modules/npm/node_modules/proc-log": { - "version": "3.0.0", + "version": "4.2.0", + "dev": true, + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "2.0.0", "dev": true, "inBundle": true, "license": "ISC", @@ -4932,12 +4931,12 @@ } }, "node_modules/npm/node_modules/promzard": { - "version": "1.0.0", + "version": "1.0.2", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "read": "^2.0.0" + "read": "^3.0.1" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -4952,12 +4951,12 @@ } }, "node_modules/npm/node_modules/read": { - "version": "2.1.0", + "version": "3.0.1", "dev": true, "inBundle": true, "license": "ISC", "dependencies": { - "mute-stream": "~1.0.0" + "mute-stream": "^1.0.0" }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -4972,21 +4971,6 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/npm/node_modules/read-package-json": { - "version": "7.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "glob": "^10.2.2", - "json-parse-even-better-errors": "^3.0.0", - "normalize-package-data": "^6.0.0", - "npm-normalize-package-bin": "^3.0.0" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/npm/node_modules/read-package-json-fast": { "version": "3.0.2", "dev": true, @@ -5017,13 +5001,10 @@ "optional": true }, "node_modules/npm/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.2", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -5031,24 +5012,6 @@ "node": ">=10" } }, - "node_modules/npm/node_modules/semver/node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/npm/node_modules/set-blocking": { - "version": "2.0.0", - "dev": true, - "inBundle": true, - "license": "ISC" - }, "node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", "dev": true, @@ -5083,17 +5046,17 @@ } }, "node_modules/npm/node_modules/sigstore": { - "version": "2.2.0", + "version": "2.3.1", "dev": true, "inBundle": true, "license": "Apache-2.0", "dependencies": { - "@sigstore/bundle": "^2.1.1", - "@sigstore/core": "^0.2.0", - "@sigstore/protobuf-specs": "^0.2.1", - "@sigstore/sign": "^2.2.1", - "@sigstore/tuf": "^2.3.0", - "@sigstore/verify": "^0.1.0" + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -5110,28 +5073,28 @@ } }, "node_modules/npm/node_modules/socks": { - "version": "2.7.1", + "version": "2.8.3", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "ip": "^2.0.0", + "ip-address": "^9.0.5", "smart-buffer": "^4.2.0" }, "engines": { - "node": ">= 10.13.0", + "node": ">= 10.0.0", "npm": ">= 3.0.0" } }, "node_modules/npm/node_modules/socks-proxy-agent": { - "version": "8.0.2", + "version": "8.0.4", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.1", "debug": "^4.3.4", - "socks": "^2.7.1" + "socks": "^2.8.3" }, "engines": { "node": ">= 14" @@ -5147,13 +5110,7 @@ "spdx-license-ids": "^3.0.0" } }, - "node_modules/npm/node_modules/spdx-exceptions": { - "version": "2.3.0", - "dev": true, - "inBundle": true, - "license": "CC-BY-3.0" - }, - "node_modules/npm/node_modules/spdx-expression-parse": { + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { "version": "3.0.1", "dev": true, "inBundle": true, @@ -5163,14 +5120,36 @@ "spdx-license-ids": "^3.0.0" } }, - "node_modules/npm/node_modules/spdx-license-ids": { - "version": "3.0.16", + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "dev": true, + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.18", "dev": true, "inBundle": true, "license": "CC0-1.0" }, + "node_modules/npm/node_modules/sprintf-js": { + "version": "1.1.3", + "dev": true, + "inBundle": true, + "license": "BSD-3-Clause" + }, "node_modules/npm/node_modules/ssri": { - "version": "10.0.5", + "version": "10.0.6", "dev": true, "inBundle": true, "license": "ISC", @@ -5248,7 +5227,7 @@ } }, "node_modules/npm/node_modules/tar": { - "version": "6.2.0", + "version": "6.2.1", "dev": true, "inBundle": true, "license": "ISC", @@ -5319,14 +5298,14 @@ } }, "node_modules/npm/node_modules/tuf-js": { - "version": "2.2.0", + "version": "2.2.1", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "@tufjs/models": "2.0.0", + "@tufjs/models": "2.0.1", "debug": "^4.3.4", - "make-fetch-happen": "^13.0.0" + "make-fetch-happen": "^13.0.1" }, "engines": { "node": "^16.14.0 || >=18.0.0" @@ -5372,14 +5351,21 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, "node_modules/npm/node_modules/validate-npm-package-name": { - "version": "5.0.0", + "version": "5.0.1", "dev": true, "inBundle": true, "license": "ISC", - "dependencies": { - "builtins": "^5.0.0" - }, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -5390,15 +5376,6 @@ "inBundle": true, "license": "ISC" }, - "node_modules/npm/node_modules/wcwidth": { - "version": "1.0.1", - "dev": true, - "inBundle": true, - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/npm/node_modules/which": { "version": "4.0.0", "dev": true, @@ -5423,15 +5400,6 @@ "node": ">=16" } }, - "node_modules/npm/node_modules/wide-align": { - "version": "1.1.5", - "dev": true, - "inBundle": true, - "license": "ISC", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, "node_modules/npm/node_modules/wrap-ansi": { "version": "8.1.0", "dev": true, @@ -5551,6 +5519,15 @@ "inBundle": true, "license": "ISC" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -5695,6 +5672,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parsimmon": { "version": "1.18.1", "resolved": "https://registry.npmjs.org/parsimmon/-/parsimmon-1.18.1.tgz", @@ -5762,6 +5772,21 @@ "node": ">=4" } }, + "node_modules/pretty-ms": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz", + "integrity": "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==", + "dev": true, + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -5822,6 +5847,35 @@ "rc": "cli.js" } }, + "node_modules/read-package-up": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", + "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", + "dev": true, + "dependencies": { + "find-up-simple": "^1.0.0", + "read-pkg": "^9.0.0", + "type-fest": "^4.6.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-package-up/node_modules/type-fest": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.25.0.tgz", + "integrity": "sha512-bRkIGlXsnGBRBQRAY56UXBm//9qH4bmJfFvq83gSz41N282df+fjy8ofcEgc1sM8geNt5cl6mC2g9Fht1cs8Aw==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/read-pkg": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", @@ -5994,35 +6048,35 @@ "dev": true }, "node_modules/semantic-release": { - "version": "23.0.0", - "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-23.0.0.tgz", - "integrity": "sha512-Jz7jEWO2igTtske112gC4PPE2whCMVrsgxUPG3/SZI7VE357suIUZFlJd1Yu0g2I6RPc2HxNEfUg7KhmDTjwqg==", + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-24.1.0.tgz", + "integrity": "sha512-FwaE2hKDHQn9G6GA7xmqsc9WnsjaFD/ppLM5PUg56Do9oKSCf+vH6cPeb3hEBV/m06n8Sh9vbVqPjHu/1onzQw==", "dev": true, "dependencies": { - "@semantic-release/commit-analyzer": "^11.0.0", + "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", - "@semantic-release/github": "^9.0.0", - "@semantic-release/npm": "^11.0.0", - "@semantic-release/release-notes-generator": "^12.0.0", + "@semantic-release/github": "^10.0.0", + "@semantic-release/npm": "^12.0.0", + "@semantic-release/release-notes-generator": "^14.0.0-beta.1", "aggregate-error": "^5.0.0", "cosmiconfig": "^9.0.0", "debug": "^4.0.0", "env-ci": "^11.0.0", - "execa": "^8.0.0", + "execa": "^9.0.0", "figures": "^6.0.0", - "find-versions": "^5.1.0", + "find-versions": "^6.0.0", "get-stream": "^6.0.0", "git-log-parser": "^1.2.0", "hook-std": "^3.0.0", "hosted-git-info": "^7.0.0", "import-from-esm": "^1.3.1", "lodash-es": "^4.17.21", - "marked": "^11.0.0", - "marked-terminal": "^6.0.0", + "marked": "^12.0.0", + "marked-terminal": "^7.0.0", "micromatch": "^4.0.2", "p-each-series": "^3.0.0", "p-reduce": "^3.0.0", - "read-pkg-up": "^11.0.0", + "read-package-up": "^11.0.0", "resolve-from": "^5.0.0", "semver": "^7.3.2", "semver-diff": "^4.0.0", @@ -6036,6 +6090,172 @@ "node": ">=20.8.1" } }, + "node_modules/semantic-release/node_modules/@octokit/auth-token": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-5.1.1.tgz", + "integrity": "sha512-rh3G3wDO8J9wSjfI436JUKzHIxq8NaiL0tVeB2aXmG6p/9859aUOAjA9pmSPNGGZxfwmaJ9ozOJImuNVJdpvbA==", + "dev": true, + "engines": { + "node": ">= 18" + } + }, + "node_modules/semantic-release/node_modules/@octokit/core": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-6.1.2.tgz", + "integrity": "sha512-hEb7Ma4cGJGEUNOAVmyfdB/3WirWMg5hDuNFVejGEDFqupeOysLc2sG6HJxY2etBp5YQu5Wtxwi020jS9xlUwg==", + "dev": true, + "dependencies": { + "@octokit/auth-token": "^5.0.0", + "@octokit/graphql": "^8.0.0", + "@octokit/request": "^9.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.0.0", + "before-after-hook": "^3.0.2", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/semantic-release/node_modules/@octokit/endpoint": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-10.1.1.tgz", + "integrity": "sha512-JYjh5rMOwXMJyUpj028cu0Gbp7qe/ihxfJMLc8VZBMMqSwLgOxDI1911gV4Enl1QSavAQNJcwmwBF9M0VvLh6Q==", + "dev": true, + "dependencies": { + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/semantic-release/node_modules/@octokit/graphql": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-8.1.1.tgz", + "integrity": "sha512-ukiRmuHTi6ebQx/HFRCXKbDlOh/7xEV6QUXaE7MJEKGNAncGI/STSbOkl12qVXZrfZdpXctx5O9X1AIaebiDBg==", + "dev": true, + "dependencies": { + "@octokit/request": "^9.0.0", + "@octokit/types": "^13.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/semantic-release/node_modules/@octokit/openapi-types": { + "version": "22.2.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-22.2.0.tgz", + "integrity": "sha512-QBhVjcUa9W7Wwhm6DBFu6ZZ+1/t/oYxqc2tp81Pi41YNuJinbFRx8B133qVOrAaBbF7D/m0Et6f9/pZt9Rc+tg==", + "dev": true + }, + "node_modules/semantic-release/node_modules/@octokit/plugin-paginate-rest": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.3.3.tgz", + "integrity": "sha512-o4WRoOJZlKqEEgj+i9CpcmnByvtzoUYC6I8PD2SA95M+BJ2x8h7oLcVOg9qcowWXBOdcTRsMZiwvM3EyLm9AfA==", + "dev": true, + "dependencies": { + "@octokit/types": "^13.5.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/semantic-release/node_modules/@octokit/plugin-retry": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-7.1.1.tgz", + "integrity": "sha512-G9Ue+x2odcb8E1XIPhaFBnTTIrrUDfXN05iFXiqhR+SeeeDMMILcAnysOsxUpEWcQp2e5Ft397FCXTcPkiPkLw==", + "dev": true, + "dependencies": { + "@octokit/request-error": "^6.0.0", + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/semantic-release/node_modules/@octokit/plugin-throttling": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-9.3.1.tgz", + "integrity": "sha512-Qd91H4liUBhwLB2h6jZ99bsxoQdhgPk6TdwnClPyTBSDAdviGPceViEgUwj+pcQDmB/rfAXAXK7MTochpHM3yQ==", + "dev": true, + "dependencies": { + "@octokit/types": "^13.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "@octokit/core": "^6.0.0" + } + }, + "node_modules/semantic-release/node_modules/@octokit/request": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-9.1.3.tgz", + "integrity": "sha512-V+TFhu5fdF3K58rs1pGUJIDH5RZLbZm5BI+MNF+6o/ssFNT4vWlCh/tVpF3NxGtP15HUxTTMUbsG5llAuU2CZA==", + "dev": true, + "dependencies": { + "@octokit/endpoint": "^10.0.0", + "@octokit/request-error": "^6.0.1", + "@octokit/types": "^13.1.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/semantic-release/node_modules/@octokit/request-error": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-6.1.4.tgz", + "integrity": "sha512-VpAhIUxwhWZQImo/dWAN/NpPqqojR6PSLgLYAituLM6U+ddx9hCioFGwBr5Mi+oi5CLeJkcAs3gJ0PYYzU6wUg==", + "dev": true, + "dependencies": { + "@octokit/types": "^13.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/semantic-release/node_modules/@octokit/types": { + "version": "13.5.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-13.5.0.tgz", + "integrity": "sha512-HdqWTf5Z3qwDVlzCrP8UJquMwunpDiMPt5er+QjGzL4hqr/vBVY/MauQgS1xWxCDT1oMx1EULyqxncdCY/NVSQ==", + "dev": true, + "dependencies": { + "@octokit/openapi-types": "^22.2.0" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/commit-analyzer": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@semantic-release/commit-analyzer/-/commit-analyzer-13.0.0.tgz", + "integrity": "sha512-KtXWczvTAB1ZFZ6B4O+w8HkfYm/OgQb1dUGNFZtDgQ0csggrmkq8sTxhd+lwGF8kMb59/RnG9o4Tn7M/I8dQ9Q==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "import-from-esm": "^1.0.3", + "lodash-es": "^4.17.21", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, "node_modules/semantic-release/node_modules/@semantic-release/error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@semantic-release/error/-/error-4.0.0.tgz", @@ -6045,6 +6265,111 @@ "node": ">=18" } }, + "node_modules/semantic-release/node_modules/@semantic-release/github": { + "version": "10.1.7", + "resolved": "https://registry.npmjs.org/@semantic-release/github/-/github-10.1.7.tgz", + "integrity": "sha512-QnhP4k1eqzYLz6a4kpWrUQeKJYXqHggveMykvUFbSquq07GF85BXvr/QLhpOD7bpDcmEfL8VnphRA7KT5i9lzQ==", + "dev": true, + "dependencies": { + "@octokit/core": "^6.0.0", + "@octokit/plugin-paginate-rest": "^11.0.0", + "@octokit/plugin-retry": "^7.0.0", + "@octokit/plugin-throttling": "^9.0.0", + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "debug": "^4.3.4", + "dir-glob": "^3.0.1", + "globby": "^14.0.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "issue-parser": "^7.0.0", + "lodash-es": "^4.17.21", + "mime": "^4.0.0", + "p-filter": "^4.0.0", + "url-join": "^5.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/npm": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/npm/-/npm-12.0.1.tgz", + "integrity": "sha512-/6nntGSUGK2aTOI0rHPwY3ZjgY9FkXmEHbW9Kr+62NVOsyqpKKeP0lrCH+tphv+EsNdJNmqqwijTEnVWUMQ2Nw==", + "dev": true, + "dependencies": { + "@semantic-release/error": "^4.0.0", + "aggregate-error": "^5.0.0", + "execa": "^9.0.0", + "fs-extra": "^11.0.0", + "lodash-es": "^4.17.21", + "nerf-dart": "^1.0.0", + "normalize-url": "^8.0.0", + "npm": "^10.5.0", + "rc": "^1.2.8", + "read-pkg": "^9.0.0", + "registry-auth-token": "^5.0.0", + "semver": "^7.1.2", + "tempy": "^3.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/release-notes-generator": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@semantic-release/release-notes-generator/-/release-notes-generator-14.0.1.tgz", + "integrity": "sha512-K0w+5220TM4HZTthE5dDpIuFrnkN1NfTGPidJFm04ULT1DEZ9WG89VNXN7F0c+6nMEpWgqmPvb7vY7JkB2jyyA==", + "dev": true, + "dependencies": { + "conventional-changelog-angular": "^8.0.0", + "conventional-changelog-writer": "^8.0.0", + "conventional-commits-filter": "^5.0.0", + "conventional-commits-parser": "^6.0.0", + "debug": "^4.0.0", + "get-stream": "^7.0.0", + "import-from-esm": "^1.0.3", + "into-stream": "^7.0.0", + "lodash-es": "^4.17.21", + "read-package-up": "^11.0.0" + }, + "engines": { + "node": ">=20.8.1" + }, + "peerDependencies": { + "semantic-release": ">=20.1.0" + } + }, + "node_modules/semantic-release/node_modules/@semantic-release/release-notes-generator/node_modules/get-stream": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-7.0.1.tgz", + "integrity": "sha512-3M8C1EOFN6r8AMUhwUAACIoXZJEOufDU5+0gFFN5uNs6XYOralD2Pqkl7m046va6x77FwposWXbAhPPIOus7mQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/semantic-release/node_modules/aggregate-error": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-5.0.0.tgz", @@ -6061,6 +6386,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/before-after-hook": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", + "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==", + "dev": true + }, "node_modules/semantic-release/node_modules/clean-stack": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-5.2.0.tgz", @@ -6076,6 +6422,61 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/conventional-changelog-angular": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.0.0.tgz", + "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "dev": true, + "dependencies": { + "compare-func": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/conventional-changelog-writer": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz", + "integrity": "sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA==", + "dev": true, + "dependencies": { + "@types/semver": "^7.5.5", + "conventional-commits-filter": "^5.0.0", + "handlebars": "^4.7.7", + "meow": "^13.0.0", + "semver": "^7.5.2" + }, + "bin": { + "conventional-changelog-writer": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/conventional-commits-filter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz", + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/semantic-release/node_modules/conventional-commits-parser": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz", + "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==", + "dev": true, + "dependencies": { + "meow": "^13.0.0" + }, + "bin": { + "conventional-commits-parser": "dist/cli/index.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/semantic-release/node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", @@ -6089,47 +6490,70 @@ } }, "node_modules/semantic-release/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.3.1.tgz", + "integrity": "sha512-gdhefCCNy/8tpH/2+ajP9IQc14vXchNdd0weyzSJEFURhRMGncQ+zKFxwjAufIewPEJm9BPOaJnvg2UtlH2gPQ==", "dev": true, "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^5.2.0", + "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" }, "engines": { - "node": ">=16.17" + "node": "^18.19.0 || >=20.5.0" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, "node_modules/semantic-release/node_modules/execa/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, "engines": { - "node": ">=16" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semantic-release/node_modules/find-versions": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-6.0.0.tgz", + "integrity": "sha512-2kCCtc+JvcZ86IGAz3Z2Y0A1baIz9fL31pH/0S1IqZr9Iwnjq8izfPtrCyQKO6TLMPELLsQMre7VDqeIKCsHkA==", + "dev": true, + "dependencies": { + "semver-regex": "^4.0.5", + "super-regex": "^1.0.0" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/semantic-release/node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true, "engines": { - "node": ">=16.17.0" + "node": ">=18.18.0" } }, "node_modules/semantic-release/node_modules/indent-string": { @@ -6145,54 +6569,87 @@ } }, "node_modules/semantic-release/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "node_modules/semantic-release/node_modules/issue-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/issue-parser/-/issue-parser-7.0.1.tgz", + "integrity": "sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==", "dev": true, + "dependencies": { + "lodash.capitalize": "^4.2.1", + "lodash.escaperegexp": "^4.1.2", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.uniqby": "^4.7.0" + }, "engines": { - "node": ">=12" + "node": "^18.17 || >=20.6.1" + } + }, + "node_modules/semantic-release/node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true, + "bin": { + "marked": "bin/marked.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 18" } }, - "node_modules/semantic-release/node_modules/npm-run-path": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", - "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", + "node_modules/semantic-release/node_modules/marked-terminal": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/marked-terminal/-/marked-terminal-7.1.0.tgz", + "integrity": "sha512-+pvwa14KZL74MVXjYdPR3nSInhGhNvPce/3mqLVZT2oUvt654sL1XImFuLZ1pkA866IYZ3ikDTOFUIC7XzpZZg==", "dev": true, "dependencies": { - "path-key": "^4.0.0" + "ansi-escapes": "^7.0.0", + "chalk": "^5.3.0", + "cli-highlight": "^2.1.11", + "cli-table3": "^0.6.5", + "node-emoji": "^2.1.3", + "supports-hyperlinks": "^3.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=16.0.0" + }, + "peerDependencies": { + "marked": ">=1 <14" + } + }, + "node_modules/semantic-release/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/semantic-release/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/semantic-release/node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, "dependencies": { - "mimic-fn": "^4.0.0" + "path-key": "^4.0.0" }, "engines": { - "node": ">=12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6235,17 +6692,23 @@ } }, "node_modules/semantic-release/node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/semantic-release/node_modules/universal-user-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.2.tgz", + "integrity": "sha512-0JCqzSKnStlRRQfCdowvqy3cy0Dvtlb8xecj/H8JFZuCze4rwjPZQOgvFvn0Ws/usCHQFGpyr+pB9adaGwXn4Q==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -6567,6 +7030,22 @@ "node": ">=0.10.0" } }, + "node_modules/super-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/super-regex/-/super-regex-1.0.0.tgz", + "integrity": "sha512-CY8u7DtbvucKuquCmOFEKhr9Besln7n9uN8eFbwcoGYWXOMW07u2o8njWaiXt11ylS3qoGF55pILjRmPlbodyg==", + "dev": true, + "dependencies": { + "function-timeout": "^1.0.1", + "time-span": "^5.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -6655,6 +7134,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -6671,6 +7171,21 @@ "xtend": "~4.0.1" } }, + "node_modules/time-span": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-5.1.0.tgz", + "integrity": "sha512-75voc/9G4rDIJleOo4jPvN4/YC4GRZrY8yy1uU4lwrB3XEQbWve8zXoO5No4eFrGcTAMYyoY67p8jRQdtA1HbA==", + "dev": true, + "dependencies": { + "convert-hrtime": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6890,6 +7405,18 @@ "engines": { "node": ">=12" } + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 3101070000..827688e6b3 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "@saithodev/semantic-release-backmerge": "^4.0.1", "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", - "gradle-semantic-release-plugin": "^1.9.1", - "semantic-release": "^23.0.0" + "gradle-semantic-release-plugin": "^1.9.2", + "semantic-release": "^24.1.0" } }