From f16e67fc75c9c3505ff875a216ce7e868022075e Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Sat, 23 Jul 2022 03:33:36 +0200 Subject: [PATCH] feat: twitter `timeline-ads` patch (#222) --- .../annotations/TimelineAdsCompatibility.kt | 13 ++++ .../TimelineTweetJsonParserFingerprint.kt | 26 +++++++ .../ad/timeline/patch/TimelineAdsPatch.kt | 77 +++++++++++++++++++ 3 files changed, 116 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt new file mode 100644 index 000000000..65831eb76 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/annotations/TimelineAdsCompatibility.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.twitter.ad.timeline.annotations + +import app.revanced.patcher.annotation.Compatibility +import app.revanced.patcher.annotation.Package + +@Compatibility( + [Package( + "com.twitter.android", arrayOf() + )] +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.RUNTIME) +internal annotation class TimelineAdsCompatibility \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt new file mode 100644 index 000000000..9c7aafa37 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/fingerprints/TimelineTweetJsonParserFingerprint.kt @@ -0,0 +1,26 @@ +package app.revanced.patches.twitter.ad.timeline.fingerprints + +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.fingerprint.method.annotation.MatchingMethod +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.twitter.ad.timeline.annotations.TimelineAdsCompatibility +import org.jf.dexlib2.Opcode + +@Name("timeline-tweet-json-parser-fingerprint") +@MatchingMethod("LJsonTimelineTweet\$\$JsonObjectMapper;", "parseField") +@TimelineAdsCompatibility +@Version("0.0.1") +object TimelineTweetJsonParserFingerprint : MethodFingerprint( + null, null, null, listOf( + Opcode.IPUT_OBJECT, + Opcode.GOTO, + Opcode.SGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.IPUT_OBJECT, + Opcode.RETURN_VOID, + ), listOf("tweetPromotedMetadata", "promotedMetadata", "hasModeratedReplies", "conversationAnnotation"), + { methodDef -> methodDef.name == "parseField" } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt new file mode 100644 index 000000000..ebc1478e6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/twitter/ad/timeline/patch/TimelineAdsPatch.kt @@ -0,0 +1,77 @@ +package app.revanced.patches.twitter.ad.timeline.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.annotation.Version +import app.revanced.patcher.data.impl.BytecodeData +import app.revanced.patcher.extensions.addInstructions +import app.revanced.patcher.extensions.instruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprintResult +import app.revanced.patcher.patch.PatchResult +import app.revanced.patcher.patch.PatchResultError +import app.revanced.patcher.patch.PatchResultSuccess +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patcher.patch.impl.BytecodePatch +import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod +import app.revanced.patches.twitter.ad.timeline.annotations.TimelineAdsCompatibility +import app.revanced.patches.twitter.ad.timeline.fingerprints.TimelineTweetJsonParserFingerprint +import org.jf.dexlib2.Opcode +import org.jf.dexlib2.builder.BuilderInstruction +import org.jf.dexlib2.builder.instruction.BuilderInstruction22c +import org.jf.dexlib2.iface.instruction.ReferenceInstruction +import org.jf.dexlib2.iface.reference.FieldReference +import org.jf.dexlib2.iface.reference.StringReference + +@Patch +@Name("timeline-ads") +@Description("Removes ads from the Twitter timeline.") +@TimelineAdsCompatibility +@Version("0.0.1") +class TimelineAdsPatch : BytecodePatch( + listOf(TimelineTweetJsonParserFingerprint) +) { + override fun execute(data: BytecodeData): PatchResult { + if (removePromotedAds()) + return PatchResultError("The instruction for the tweet id field could not be found") + + return PatchResultSuccess() + } + + private fun removePromotedAds(): Boolean { + val (parserFingerprintResult, parserMethod, instructions) = TimelineTweetJsonParserFingerprint.unwrap() + + // Anchor index + val tweetIdFieldInstructionIndex = instructions.indexOfFirst { instruction -> + if (instruction.opcode.ordinal != Opcode.CONST_STRING.ordinal) return@indexOfFirst false + if (((instruction as? ReferenceInstruction)?.reference as StringReference).string != "tweetSocialProof") return@indexOfFirst false + + // Use the above conditions as an anchor to find the index for the instruction with the field we need + return@indexOfFirst true + } - 2 // This is where the instruction with the field is located + + // Reference to the tweetId field for of the timeline tweet + val tweetIdFieldReference = + (parserMethod.instruction(tweetIdFieldInstructionIndex) as? BuilderInstruction22c)?.reference as? FieldReference + ?: return true + + // Set the tweetId field to null + // This will cause twitter to not show the promoted ads, because we set it to null, when the tweet is promoted + parserFingerprintResult.mutableMethod.addInstructions( + parserFingerprintResult.patternScanResult!!.startIndex + 1, + """ + const/4 v2, 0x0 + iput-object v2, p0, Lcom/twitter/model/json/timeline/urt/JsonTimelineTweet;->${tweetIdFieldReference.name}:Ljava/lang/String; + """ + ) + return false + } + + private fun MethodFingerprint.unwrap(): Triple> { + val parserFingerprintResult = this.result!! + val parserMethod = parserFingerprintResult.mutableMethod + val instructions = parserMethod.implementation!!.instructions + + return Triple(parserFingerprintResult, parserMethod, instructions) + } +}