diff --git a/.github/workflows/pull_strings.yml b/.github/workflows/pull_strings.yml new file mode 100644 index 000000000..8202912d0 --- /dev/null +++ b/.github/workflows/pull_strings.yml @@ -0,0 +1,34 @@ +name: Pull strings + +on: + workflow_dispatch: + schedule: + - cron: 0 * 1 * * + +jobs: + pull: + name: Pull strings + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Pull strings + uses: crowdin/github-action@v1 + with: + config: crowdin.yml + download_translations: true + localization_branch_name: feat/translations + create_pull_request: true + pull_request_title: "chore: Sync translations" + pull_request_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)" + pull_request_base_branch_name: "dev" + commit_message: "chore: Sync translations" + github_user_name: revanced-bot + github_user_email: github@revanced.app + env: + GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/.github/workflows/push_strings.yml b/.github/workflows/push_strings.yml new file mode 100644 index 000000000..ca238da18 --- /dev/null +++ b/.github/workflows/push_strings.yml @@ -0,0 +1,27 @@ +name: Push strings + +on: + workflow_dispatch: + push: + paths: + - /src/main/resources/addresources/values/strings.xml + +jobs: + push: + name: Push strings + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Push strings + uses: crowdin/github-action@v1 + with: + config: crowdin.yml + upload_sources: true + env: + GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} + CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} + CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} diff --git a/.github/workflows/sync_crowdin.yml b/.github/workflows/sync_crowdin.yml deleted file mode 100644 index e0d0912b7..000000000 --- a/.github/workflows/sync_crowdin.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Sync Crowdin - -on: - workflow_dispatch: - schedule: - - cron: 0 * 1 * * - push: - paths: - - /src/main/resources/addresources/values/strings.xml - -jobs: - sync: - name: Sync translations - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Sync translations - uses: crowdin/github-action@v1 - with: - config: crowdin.yml - upload_sources: true - upload_translations: false - download_translations: true - localization_branch_name: feat/translations - create_pull_request: true - pull_request_title: "chore: Sync translations" - pull_request_body: "Sync translations from [crowdin.com/project/revanced](https://crowdin.com/project/revanced)" - pull_request_base_branch_name: "dev" - commit_message: "chore: Sync translations" - github_user_name: revanced-bot - github_user_email: github@revanced.app - env: - GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }} - CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }} - CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 47f78f559..8a0c9e0f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,24 @@ +# [4.6.0-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.5.1-dev.2...v4.6.0-dev.1) (2024-03-31) + + +### Features + +* **Tumblr:** Add `Fix old versions` patch ([#2954](https://github.com/ReVanced/revanced-patches/issues/2954)) ([2fde60e](https://github.com/ReVanced/revanced-patches/commit/2fde60eceb0a96fa857c32cd55c1fd7fe776a679)) + +## [4.5.1-dev.2](https://github.com/ReVanced/revanced-patches/compare/v4.5.1-dev.1...v4.5.1-dev.2) (2024-03-30) + + +### Bug Fixes + +* **Mi Fitness - Fix login:** Patch correct register ([#2942](https://github.com/ReVanced/revanced-patches/issues/2942)) ([dc96942](https://github.com/ReVanced/revanced-patches/commit/dc969422b5d50f21e6ea7a64b67dfc650fee6e36)) + +## [4.5.1-dev.1](https://github.com/ReVanced/revanced-patches/compare/v4.5.0...v4.5.1-dev.1) (2024-03-30) + + +### Bug Fixes + +* **Tumblr:** Restore compatibility with latest versions ([#2955](https://github.com/ReVanced/revanced-patches/issues/2955)) ([2954ba7](https://github.com/ReVanced/revanced-patches/commit/2954ba78d21d77308404961f79234bbec606d42e)) + # [4.5.0](https://github.com/ReVanced/revanced-patches/compare/v4.4.0...v4.5.0) (2024-03-30) diff --git a/api/revanced-patches.api b/api/revanced-patches.api index 274be1988..4bba02a52 100644 --- a/api/revanced-patches.api +++ b/api/revanced-patches.api @@ -1056,6 +1056,16 @@ public final class app/revanced/patches/tumblr/featureflags/OverrideFeatureFlags public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V } +public final class app/revanced/patches/tumblr/fixes/FixOldVersionsPatch : app/revanced/patcher/patch/BytecodePatch { + public static final field INSTANCE Lapp/revanced/patches/tumblr/fixes/FixOldVersionsPatch; + public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V + public synthetic fun execute (Lapp/revanced/patcher/data/Context;)V +} + +public final class app/revanced/patches/tumblr/fixes/fingerprints/HttpPathParserFingerprint : app/revanced/patcher/fingerprint/MethodFingerprint { + public static final field INSTANCE Lapp/revanced/patches/tumblr/fixes/fingerprints/HttpPathParserFingerprint; +} + public final class app/revanced/patches/tumblr/live/DisableTumblrLivePatch : app/revanced/patcher/patch/BytecodePatch { public static final field INSTANCE Lapp/revanced/patches/tumblr/live/DisableTumblrLivePatch; public fun execute (Lapp/revanced/patcher/data/BytecodeContext;)V diff --git a/gradle.properties b/gradle.properties index 03e140172..4c62d5701 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.parallel = true org.gradle.caching = true kotlin.code.style = official -version = 4.5.0 +version = 4.6.0-dev.1 diff --git a/src/main/kotlin/app/revanced/patches/mifitness/misc/login/FixLoginPatch.kt b/src/main/kotlin/app/revanced/patches/mifitness/misc/login/FixLoginPatch.kt index cf67c893d..fdb8f315a 100644 --- a/src/main/kotlin/app/revanced/patches/mifitness/misc/login/FixLoginPatch.kt +++ b/src/main/kotlin/app/revanced/patches/mifitness/misc/login/FixLoginPatch.kt @@ -2,13 +2,11 @@ package app.revanced.patches.mifitness.misc.login import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.extensions.InstructionExtensions.addInstruction -import app.revanced.patcher.extensions.InstructionExtensions.getInstruction import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.Patch import app.revanced.patches.mifitness.misc.login.fingerprints.XiaomiAccountManagerConstructorFingerprint import app.revanced.util.exception -import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction @Patch( name = "Fix login", @@ -20,16 +18,9 @@ object FixLoginPatch : BytecodePatch( setOf(XiaomiAccountManagerConstructorFingerprint), ) { override fun execute(context: BytecodeContext) { - XiaomiAccountManagerConstructorFingerprint.result?.let { - it.mutableMethod.apply { - val isCertifiedIndex = it.scanResult.patternScanResult!!.startIndex - val isCertifiedRegister = getInstruction(isCertifiedIndex).registerA - - addInstruction( - isCertifiedIndex, - "const/4 p$isCertifiedRegister, 0x0", - ) - } - } ?: throw XiaomiAccountManagerConstructorFingerprint.exception + XiaomiAccountManagerConstructorFingerprint.result?.mutableMethod?.addInstruction( + 0, + "const/16 p2, 0x0", + ) ?: throw XiaomiAccountManagerConstructorFingerprint.exception } } diff --git a/src/main/kotlin/app/revanced/patches/mifitness/misc/login/fingerprints/XiaomiAccountManagerConstructorFingerprint.kt b/src/main/kotlin/app/revanced/patches/mifitness/misc/login/fingerprints/XiaomiAccountManagerConstructorFingerprint.kt index e5d06acef..d81aa33f7 100644 --- a/src/main/kotlin/app/revanced/patches/mifitness/misc/login/fingerprints/XiaomiAccountManagerConstructorFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/mifitness/misc/login/fingerprints/XiaomiAccountManagerConstructorFingerprint.kt @@ -3,12 +3,14 @@ package app.revanced.patches.mifitness.misc.login.fingerprints import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint import com.android.tools.smali.dexlib2.AccessFlags -import com.android.tools.smali.dexlib2.Opcode internal object XiaomiAccountManagerConstructorFingerprint : MethodFingerprint( accessFlags = AccessFlags.PRIVATE or AccessFlags.CONSTRUCTOR, customFingerprint = { methodDef, _ -> methodDef.definingClass == "Lcom/xiaomi/passport/accountmanager/XiaomiAccountManager;" }, - opcodes = listOf(Opcode.IF_NEZ), + parameters = listOf( + "Landroid/content/Context;", + "Z", + ), ) diff --git a/src/main/kotlin/app/revanced/patches/tumblr/annoyances/popups/fingerprints/ShowGiftMessagePopupFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/annoyances/popups/fingerprints/ShowGiftMessagePopupFingerprint.kt index c65cd0291..dc5a168a6 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/annoyances/popups/fingerprints/ShowGiftMessagePopupFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/annoyances/popups/fingerprints/ShowGiftMessagePopupFingerprint.kt @@ -1,9 +1,12 @@ package app.revanced.patches.tumblr.annoyances.popups.fingerprints +import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags // This method is responsible for loading and displaying the visual Layout of the Gift Message Popup. internal object ShowGiftMessagePopupFingerprint : MethodFingerprint( - strings = listOf("activity", "anchorView"), - customFingerprint = { methodDef, _ -> methodDef.definingClass.endsWith("GiftMessagePopup;") } + strings = listOf("activity", "anchorView", "textMessage"), + returnType = "V", + accessFlags = AccessFlags.FINAL or AccessFlags.PUBLIC ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/featureflags/OverrideFeatureFlagsPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/featureflags/OverrideFeatureFlagsPatch.kt index 267ab358a..94367009c 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/featureflags/OverrideFeatureFlagsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/featureflags/OverrideFeatureFlagsPatch.kt @@ -28,6 +28,9 @@ object OverrideFeatureFlagsPatch : BytecodePatch( internal lateinit var addOverride: (name: String, value: String) -> Unit private set override fun execute(context: BytecodeContext) = GetFeatureValueFingerprint.result?.let { + val configurationClass = it.method.definingClass + val featureClass = it.method.parameterTypes[0].toString() + // The method we want to inject into does not have enough registers, so we inject a helper method // and inject more instructions into it later, see addOverride. // This is not in an integration since the unused variable would get compiled away and the method would @@ -35,7 +38,7 @@ object OverrideFeatureFlagsPatch : BytecodePatch( val helperMethod = ImmutableMethod( it.method.definingClass, "getValueOverride", - listOf(ImmutableMethodParameter("Lcom/tumblr/configuration/Feature;", null, "feature")), + listOf(ImmutableMethodParameter(featureClass, null, "feature")), "Ljava/lang/String;", AccessFlags.PUBLIC or AccessFlags.FINAL, null, @@ -50,7 +53,7 @@ object OverrideFeatureFlagsPatch : BytecodePatch( 0, """ # toString() the enum value - invoke-virtual {p1}, Lcom/tumblr/configuration/Feature;->toString()Ljava/lang/String; + invoke-virtual {p1}, $featureClass->toString()Ljava/lang/String; move-result-object v0 # !!! If you add more instructions above this line, update helperInsertIndex below! @@ -75,7 +78,7 @@ object OverrideFeatureFlagsPatch : BytecodePatch( getFeatureIndex, """ # Call the Helper Method with the Feature - invoke-virtual {p0, p1}, Lcom/tumblr/configuration/Configuration;->getValueOverride(Lcom/tumblr/configuration/Feature;)Ljava/lang/String; + invoke-virtual {p0, p1}, $configurationClass->getValueOverride($featureClass)Ljava/lang/String; move-result-object v0 # If it returned null, skip if-eqz v0, :is_null diff --git a/src/main/kotlin/app/revanced/patches/tumblr/featureflags/fingerprints/GetFeatureValueFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/featureflags/fingerprints/GetFeatureValueFingerprint.kt index d0274125d..0088c3aa6 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/featureflags/fingerprints/GetFeatureValueFingerprint.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/featureflags/fingerprints/GetFeatureValueFingerprint.kt @@ -1,6 +1,8 @@ package app.revanced.patches.tumblr.featureflags.fingerprints +import app.revanced.patcher.extensions.or import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode // This fingerprint targets the method to get the value of a Feature in the class "com.tumblr.configuration.Feature". @@ -19,5 +21,7 @@ internal object GetFeatureValueFingerprint : MethodFingerprint( Opcode.INVOKE_STATIC, Opcode.MOVE_RESULT ), - customFingerprint = { method, _ -> method.definingClass == "Lcom/tumblr/configuration/Configuration;" } + returnType = "Ljava/lang/String;", + parameters = listOf("L", "Z"), + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL ) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/fixes/FixOldVersionsPatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/fixes/FixOldVersionsPatch.kt new file mode 100644 index 000000000..9fe883d9e --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tumblr/fixes/FixOldVersionsPatch.kt @@ -0,0 +1,38 @@ +package app.revanced.patches.tumblr.fixes + +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotation.CompatiblePackage +import app.revanced.patcher.patch.annotation.Patch +import app.revanced.patches.tumblr.fixes.fingerprints.HttpPathParserFingerprint +import app.revanced.util.exception + +@Patch( + name = "Fix old versions", + description = "Fixes old versions of the app (v33.2 and earlier) breaking due to Tumblr removing remnants of Tumblr" + + " Live from the API, which causes many requests to fail. This patch has no effect on newer versions of the app.", + compatiblePackages = [CompatiblePackage("com.tumblr")], + use = false, +) +@Suppress("unused") +object FixOldVersionsPatch : BytecodePatch( + setOf(HttpPathParserFingerprint), +) { + override fun execute(context: BytecodeContext) = + HttpPathParserFingerprint.result?.let { + val endIndex = it.scanResult.patternScanResult!!.endIndex + + it.mutableMethod.addInstructions( + endIndex + 1, + """ + # Remove "?live_now" from the request path p2. + # p2 = p2.replace(p1, p3) + const-string p1, ",?live_now" + const-string p3, "" + invoke-virtual {p2, p1, p3}, Ljava/lang/String;->replace(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String; + move-result-object p2 + """, + ) + } ?: throw HttpPathParserFingerprint.exception +} diff --git a/src/main/kotlin/app/revanced/patches/tumblr/fixes/fingerprints/HttpPathParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/tumblr/fixes/fingerprints/HttpPathParserFingerprint.kt new file mode 100644 index 000000000..0ef376ed9 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/tumblr/fixes/fingerprints/HttpPathParserFingerprint.kt @@ -0,0 +1,15 @@ +package app.revanced.patches.tumblr.fixes.fingerprints + +import app.revanced.patcher.fingerprint.MethodFingerprint +import com.android.tools.smali.dexlib2.Opcode + +// Fingerprint for the parseHttpMethodAndPath from retrofit2 +// https://github.com/square/retrofit/blob/ebf87b10997e2136af4d335276fa950221852c64/retrofit/src/main/java/retrofit2/RequestFactory.java#L270-L302 +// Injecting here allows modifying the path/query params of API endpoints defined via annotations +object HttpPathParserFingerprint : MethodFingerprint( + strings = listOf("Only one HTTP method is allowed. Found: %s and %s."), + opcodes = listOf( + Opcode.IPUT_OBJECT, + Opcode.IPUT_BOOLEAN + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt b/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt index 8cbdaca6e..f5b53133a 100644 --- a/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt +++ b/src/main/kotlin/app/revanced/patches/tumblr/live/DisableTumblrLivePatch.kt @@ -8,11 +8,11 @@ import app.revanced.patches.tumblr.featureflags.OverrideFeatureFlagsPatch import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch @Patch( - name = "Disable Tumblr Live", description = "Disable the Tumblr Live tab button and dashboard carousel.", dependencies = [OverrideFeatureFlagsPatch::class, TimelineFilterPatch::class], compatiblePackages = [CompatiblePackage("com.tumblr")], ) +@Deprecated("Tumblr Live was removed and is no longer served in the feed, making this patch useless.") @Suppress("unused") object DisableTumblrLivePatch : BytecodePatch(emptySet()) { override fun execute(context: BytecodeContext) { diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt index 00d9dfe61..904de9c05 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/hide/player/flyoutmenupanel/HidePlayerFlyoutMenuPatch.kt @@ -46,7 +46,6 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch ) @Suppress("unused") object HidePlayerFlyoutMenuPatch : ResourcePatch() { - private const val KEY = "revanced_hide_player_flyout" private const val FILTER_CLASS_DESCRIPTOR = "Lapp/revanced/integrations/youtube/patches/components/PlayerFlyoutMenuItemsFilter;" @@ -55,21 +54,21 @@ object HidePlayerFlyoutMenuPatch : ResourcePatch() { AddResourcesPatch(this::class) SettingsPatch.PreferenceScreen.PLAYER.addPreferences( - PreferenceScreen( - key = KEY, - preferences = setOf( - SwitchPreference("${KEY}_captions"), - SwitchPreference("${KEY}_additional_settings"), - SwitchPreference("${KEY}_loop_video"), - SwitchPreference("${KEY}_ambient_mode"), - SwitchPreference("${KEY}_report"), - SwitchPreference("${KEY}_help"), - SwitchPreference("${KEY}_speed"), - SwitchPreference("${KEY}_more_info"), - SwitchPreference("${KEY}_audio_track"), - SwitchPreference("${KEY}_watch_in_vr"), - ), - ) + PreferenceScreen( + key = "revanced_hide_player_flyout", + preferences = setOf( + SwitchPreference("revanced_hide_player_flyout_captions"), + SwitchPreference("revanced_hide_player_flyout_additional_settings"), + SwitchPreference("revanced_hide_player_flyout_loop_video"), + SwitchPreference("revanced_hide_player_flyout_ambient_mode"), + SwitchPreference("revanced_hide_player_flyout_report"), + SwitchPreference("revanced_hide_player_flyout_help"), + SwitchPreference("revanced_hide_player_flyout_speed"), + SwitchPreference("revanced_hide_player_flyout_more_info"), + SwitchPreference("revanced_hide_player_flyout_audio_track"), + SwitchPreference("revanced_hide_player_flyout_watch_in_vr"), + ) + ) ) LithoFilterPatch.addFilter(FILTER_CLASS_DESCRIPTOR) diff --git a/src/main/resources/addresources/values/strings.xml b/src/main/resources/addresources/values/strings.xml index b58754755..50e59bf27 100644 --- a/src/main/resources/addresources/values/strings.xml +++ b/src/main/resources/addresources/values/strings.xml @@ -13,7 +13,9 @@ Import failed: %s + GmsCore is not installed. Install it. + Follow the \"Don\'t kill my app\" guide for GmsCore. Action needed GmsCore is not whitelisted from battery optimization.\n\nFollow the \"Don\'t kill my app\" guide for GmsCore. @@ -187,8 +189,8 @@ Keywords and phrases to hide, separated by new lines\n\nWords with uppercase letters in the middle must be entered with the casing (ie: iPhone, TikTok, LeBlanc) About keyword filtering Home/Subscription/Search results are filtered to hide content that matches keyword phrases\n\nLimitations\n• Some Shorts may not be hidden\n• Some UI components may not be hidden\n• Searching for a keyword may show no results - Invalid keyword. Cannot use: \'%s\' as a filter - Invalid keyword. \'%s\' is less than %s characters + Invalid keyword. Cannot use: \'%s\' as a filter + Invalid keyword. \'%1$s\' is less than %2$s characters Hide general ads @@ -466,9 +468,6 @@ Button is hidden Button is shown - - Video - Disable rolling number animations Rolling numbers are not animated @@ -564,16 +563,16 @@ Return YouTube Dislike Hidden Dislikes temporarily not available (API timed out) - Dislikes not available (status %d) + Dislikes not available (status %d) Dislikes not available (client API limit reached) Dislikes not available (%s) - + Reload video to vote using ReturnYouTubeDislike Return YouTube Dislike Dislikes are shown Dislikes are not shown Show dislikes on Shorts + Dislikes shown on Shorts %s Dislikes hidden on Shorts Limitation: Dislikes may not appear in incognito mode @@ -589,6 +588,7 @@ About ReturnYouTubeDislike.com Data is provided by the Return YouTube Dislike API. Tap here to learn more + ReturnYouTubeDislike API statistics of this device API response time, average API response time, minimum @@ -599,8 +599,7 @@ No network calls made %d network calls made API fetch votes, number of timeouts - No - network calls timed out + No network calls timed out %d network calls timed out API client rate limits No client rate limits encountered @@ -731,19 +730,19 @@ Show a skip button Show in seek bar Disable - Unable to submit segment: %s + Unable to submit segment: %s SponsorBlock is temporarily down - Unable to submit segment (status: %d %s) + Unable to submit segment (status: %1$d %2$s) Unable to submit segment.\nRate Limited (too many from the same user or IP) - Can\'t submit the segment: %s + Can\'t submit the segment: %s Can\'t submit the segment.\nAlready exists Segment submitted successfully SponsorBlock temporarily not available - SponsorBlock temporarily not available (status %d) + SponsorBlock temporarily not available (status %d) SponsorBlock temporarily not available (API timed out) Unable to vote for segment (API timed out) - Unable to vote for segment (status: %d %s) - Unable to vote for segment: %s + Unable to vote for segment (status: %1$d %2$s) + Unable to vote for segment: %s Upvote Downvote Change category @@ -751,14 +750,14 @@ Choose the segment category Category is disabled in settings. Enable category to submit. New SponsorBlock segment - Set %02d:%02d:%03d as the start or end of a new segment? + Set %1$02d:%2$02d:%3$03d as the start or end of a new segment? start end now Time the segment begins at Time the segment ends at Are the times correct? - The segment lasts from %02d:%02d to %02d:%02d (%d minutes %02d seconds)\nIs it ready to submit? + The segment lasts from %1$02d:%2$02d to %3$02d:%4$02d (%5$d minutes %6$02d seconds)\nIs it ready to submit? Start must be before the end Mark two locations on the time bar first Preview the segment, and ensure it skips smoothly @@ -769,22 +768,22 @@ Stats temporarily not available (API is down) Loading... SponsorBlock is disabled - Your username: <b>%s</b> + Your username: <b>%s</b> Tap here to change your username - Unable to change username: Status: %d %s + Unable to change username: Status: %1$d %2$s Username successfully changed - Your reputation is <b>%.2f</b> - You\'ve created <b>%s</b> segments + Your reputation is <b>%.2f</b> + You\'ve created <b>%s</b> segments SponsorBlock leaderboard - You\'ve saved people from <b>%s</b> segments + You\'ve saved people from <b>%s</b> segments Tap here to see the global stats and top contributors - That\'s <b>%s</b> of their lives.<br>Tap here to see the leaderboard - You\'ve skipped <b>%s</b> segments - That\'s <b>%s</b> + That\'s <b>%s</b> of their lives.<br>Tap here to see the leaderboard + You\'ve skipped <b>%s</b> segments + That\'s <b>%s</b> Reset skipped segments counter? - %s hours %s minutes - %s minutes %s seconds - %s seconds + %1$s hours %2$s minutes + %1$s minutes %2$s seconds + %s seconds Color: Color changed Color reset @@ -800,7 +799,10 @@ Version spoofed Version not spoofed App version will be spoofed to an older version of YouTube.\n\nThis will change the appearance and features of the app, but unknown side effects may occur.\n\nIf later turned off, it is recommended to clear the app data to prevent UI bugs. + Spoof app version target + 18.33.40 - Restore RYD on Shorts incognito mode 18.20.39 - Restore wide video speed & quality menu 18.09.39 - Restore library tab @@ -897,6 +899,7 @@ Device dimensions spoofed Device dimensions not spoofed\n\nSpoofing the device dimensions can unlock higher video qualities but unknown side effects may occur + Spoof app signature Spoof the app signature to prevent playback issues @@ -941,6 +944,7 @@ Haptics are disabled Haptics are enabled + Enable auto HDR brightness Auto HDR brightness is enabled @@ -963,12 +967,12 @@ Default video quality on mobile network mobile wifi - Changed default %s quality to: %s + Changed default %1$s quality to: %2$s Custom playback speeds Add or change the available playback speeds - Custom speeds must be less than %s Using default values. + Custom speeds must be less than %s Using default values. Invalid custom playback speeds. Using default values. @@ -976,7 +980,7 @@ Playback speed changes apply to all videos Playback speed changes only apply to the current video Default playback speed - Changed default speed to: %s + Changed default speed to: %s Restore old video quality menu