From 723964bc29d0a9edc7ad1915c9cf5669fe9e6f95 Mon Sep 17 00:00:00 2001 From: StanPonomarev Date: Fri, 21 Aug 2026 20:49:42 +0200 Subject: [PATCH] feat: remove upgrade button --- patches-list.json | 28 +++++++++++++++- .../patches/MorphePatchIntegrationTest.kt | 13 +++++--- .../RemoveTimelineUpgradeButtonPatch.kt | 32 +++++++++++++++++++ 3 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 patches/src/main/kotlin/app/twitter/patches/RemoveTimelineUpgradeButtonPatch.kt diff --git a/patches-list.json b/patches-list.json index dfa4077..529498a 100644 --- a/patches-list.json +++ b/patches-list.json @@ -27,6 +27,32 @@ } ], "options": [] + }, + { + "name": "Remove timeline Upgrade button", + "description": "Removes the Premium Upgrade button from the timeline's top bar.", + "default": true, + "dependencies": [], + "compatiblePackages": [ + { + "packageName": "com.twitter.android", + "name": "X", + "description": null, + "apkFileType": "APKM", + "appIconColor": "#000000", + "signatures": null, + "targets": [ + { + "version": "12.7.1-release.0", + "versionCodes": null, + "isExperimental": false, + "minSdk": 28, + "description": null + } + ] + } + ], + "options": [] } ] -} +} \ No newline at end of file diff --git a/patches/src/integrationTest/kotlin/app/twitter/patches/MorphePatchIntegrationTest.kt b/patches/src/integrationTest/kotlin/app/twitter/patches/MorphePatchIntegrationTest.kt index db77abd..5e4e6ad 100644 --- a/patches/src/integrationTest/kotlin/app/twitter/patches/MorphePatchIntegrationTest.kt +++ b/patches/src/integrationTest/kotlin/app/twitter/patches/MorphePatchIntegrationTest.kt @@ -23,7 +23,7 @@ import kotlin.test.assertTrue class MorphePatchIntegrationTest { @Test - fun `applies outdated version patch to target APKM`() { + fun `applies patches to target APKM`() { val inputApkm = requiredPathProperty("integrationTest.inputApkm") val patchBundle = requiredPathProperty("integrationTest.patchBundle") val morpheCache = requiredPathProperty("integrationTest.morpheCache") @@ -47,7 +47,8 @@ class MorphePatchIntegrationTest { "patch", "--patches=${patchBundle.absolutePathString()}", "--exclusive", - "--enable=$PATCH_NAME", + "--enable=$OUTDATED_PATCH_NAME", + "--enable=$UPGRADE_PATCH_NAME", "--unsigned", "--out=${patchedApk.absolutePathString()}", "--result-file=${resultFile.absolutePathString()}", @@ -91,7 +92,10 @@ class MorphePatchIntegrationTest { val appliedPatchNames = result.getAsJsonArray("appliedPatches") .map { it.asJsonObject.get("name").asString } - assertTrue(PATCH_NAME in appliedPatchNames, "Expected patch was not applied: $appliedPatchNames") + assertTrue( + appliedPatchNames.containsAll(listOf(OUTDATED_PATCH_NAME, UPGRADE_PATCH_NAME)), + "Expected patches were not applied: $appliedPatchNames" + ) assertTrue(result.getAsJsonArray("failedPatches").isEmpty, "Morphe reported failed patches") } finally { workDirectory.toFile().deleteRecursively() @@ -142,7 +146,8 @@ class MorphePatchIntegrationTest { } private companion object { - const val PATCH_NAME = "Disable outdated version screen" + const val OUTDATED_PATCH_NAME = "Disable outdated version screen" + const val UPGRADE_PATCH_NAME = "Remove timeline Upgrade button" const val MORPHE_VERSION = "1.13.1" const val MORPHE_SHA256 = "a212531f68d3ada4b504bf9dac3e02460589f3bf8868ee66e1b4780152d3466b" const val MORPHE_URL = diff --git a/patches/src/main/kotlin/app/twitter/patches/RemoveTimelineUpgradeButtonPatch.kt b/patches/src/main/kotlin/app/twitter/patches/RemoveTimelineUpgradeButtonPatch.kt new file mode 100644 index 0000000..eb34da7 --- /dev/null +++ b/patches/src/main/kotlin/app/twitter/patches/RemoveTimelineUpgradeButtonPatch.kt @@ -0,0 +1,32 @@ +package app.twitter.patches + +import app.morphe.patcher.patch.resourcePatch +import app.twitter.patches.shared.Constants.COMPATIBILITY_X +import org.w3c.dom.Element + +private const val TOOLBAR_ITEMS_PATH = "res/menu/toolbar_items.xml" +private const val UPGRADE_BUTTON_ID = "premium_upsell_upgrade" + +@Suppress("unused") +val removeTimelineUpgradeButtonPatch = resourcePatch( + name = "Remove timeline Upgrade button", + description = "Removes the Premium Upgrade button from the timeline's top bar.", + default = true +) { + compatibleWith(COMPATIBILITY_X) + + execute { + document(TOOLBAR_ITEMS_PATH).use { document -> + val menu = document.documentElement + val upgradeButton = (0 until menu.childNodes.length) + .asSequence() + .map { menu.childNodes.item(it) } + .filterIsInstance() + .single { + it.getAttribute("android:id").substringAfterLast('/') == UPGRADE_BUTTON_ID + } + + menu.removeChild(upgradeButton) + } + } +}