From 8ba9a19ade24c5fe9bd6d4e49772b7663522780e Mon Sep 17 00:00:00 2001 From: brosssh <44944126+brosssh@users.noreply.github.com> Date: Thu, 18 Sep 2025 08:13:46 +0200 Subject: [PATCH] feat(Instagram): Add `Limit feed to followed profiles` patch (#5908) --- extensions/instagram/build.gradle.kts | 3 + .../instagram/src/main/AndroidManifest.xml | 1 + .../feed/LimitFeedToFollowedProfiles.java | 18 ++++++ patches/api/patches.api | 8 +++ .../patches/instagram/feed/Fingerprints.kt | 20 ++++++ .../feed/LimitFeedToFollowedProfiles.kt | 63 +++++++++++++++++++ .../hide/navigation/HideNavigationButtons.kt | 2 +- .../misc/extension/SharedExtensionPatch.kt | 9 +++ .../extension/hooks/ApplicationInitHook.kt | 9 +++ 9 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 extensions/instagram/build.gradle.kts create mode 100644 extensions/instagram/src/main/AndroidManifest.xml create mode 100644 extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/feed/Fingerprints.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/feed/LimitFeedToFollowedProfiles.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/SharedExtensionPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/hooks/ApplicationInitHook.kt diff --git a/extensions/instagram/build.gradle.kts b/extensions/instagram/build.gradle.kts new file mode 100644 index 000000000..8cf6305c1 --- /dev/null +++ b/extensions/instagram/build.gradle.kts @@ -0,0 +1,3 @@ +dependencies { + compileOnly(project(":extensions:shared:library")) +} diff --git a/extensions/instagram/src/main/AndroidManifest.xml b/extensions/instagram/src/main/AndroidManifest.xml new file mode 100644 index 000000000..9b65eb06c --- /dev/null +++ b/extensions/instagram/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java new file mode 100644 index 000000000..2367e738a --- /dev/null +++ b/extensions/instagram/src/main/java/app/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles.java @@ -0,0 +1,18 @@ +package app.revanced.extension.instagram.feed; + +import java.util.HashMap; +import java.util.Map; + +@SuppressWarnings("unused") +public class LimitFeedToFollowedProfiles { + + /** + * Injection point. + */ + public static Map setFollowingHeader(Map requestHeaderMap) { + // Create new map as original is unmodifiable. + Map patchedRequestHeaderMap = new HashMap<>(requestHeaderMap); + patchedRequestHeaderMap.put("pagination_source", "following"); + return patchedRequestHeaderMap; + } +} diff --git a/patches/api/patches.api b/patches/api/patches.api index 00b58e88e..f54cb57e8 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -264,6 +264,10 @@ public final class app/revanced/patches/instagram/ads/HideAdsPatchKt { public static final fun getHideAdsPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/instagram/feed/LimitFeedToFollowedProfilesKt { + public static final fun getLimitFeedToFollowedProfiles ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/instagram/hide/explore/HideExploreFeedKt { public static final fun getHideExportFeedPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } @@ -276,6 +280,10 @@ public final class app/revanced/patches/instagram/hide/stories/HideStoriesKt { public static final fun getHideStoriesPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/instagram/misc/extension/SharedExtensionPatchKt { + public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; +} + public final class app/revanced/patches/instagram/misc/signature/SignatureCheckPatchKt { public static final fun getSignatureCheckPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/feed/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/feed/Fingerprints.kt new file mode 100644 index 000000000..cf8c61118 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/feed/Fingerprints.kt @@ -0,0 +1,20 @@ +package app.revanced.patches.instagram.feed + +import app.revanced.patcher.fingerprint +import app.revanced.patcher.patch.BytecodePatchContext + +internal val mainFeedRequestClassFingerprint = fingerprint { + strings("Request{mReason=", ", mInstanceNumber=") +} + +context(BytecodePatchContext) +internal val initMainFeedRequestFingerprint get() = fingerprint { + custom { method, classDef -> + method.name == "" && + classDef == mainFeedRequestClassFingerprint.classDef + } +} + +internal val mainFeedHeaderMapFinderFingerprint = fingerprint { + strings("pagination_source", "FEED_REQUEST_SENT") +} diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/feed/LimitFeedToFollowedProfiles.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/feed/LimitFeedToFollowedProfiles.kt new file mode 100644 index 000000000..3ded4c91a --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/feed/LimitFeedToFollowedProfiles.kt @@ -0,0 +1,63 @@ +package app.revanced.patches.instagram.feed + +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.patch.bytecodePatch +import app.revanced.patches.instagram.misc.extension.sharedExtensionPatch +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction +import com.android.tools.smali.dexlib2.iface.reference.FieldReference + +internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/instagram/feed/LimitFeedToFollowedProfiles;" + +@Suppress("unused") +val limitFeedToFollowedProfiles = bytecodePatch( + name = "Limit feed to followed profiles", + description = "Filters the home feed to display only content from profiles you follow.", +) { + compatibleWith("com.instagram.android") + + dependsOn(sharedExtensionPatch) + + execute { + /** + * Since the header field is obfuscated and there is no easy way to identify it among all the class fields, + * an additional method is fingerprinted. + * This method uses the map, so we can get the field name of the map field using this. + */ + val mainFeedRequestHeaderFieldName: String + + with(mainFeedHeaderMapFinderFingerprint.method) { + mainFeedRequestHeaderFieldName = indexOfFirstInstructionOrThrow { + getReference().let { ref -> + ref?.type == "Ljava/util/Map;" && + ref.definingClass == mainFeedRequestClassFingerprint.classDef.toString() + + } + }.let { instructionIndex -> + getInstruction(instructionIndex).getReference()!!.name + } + } + + initMainFeedRequestFingerprint.method.apply { + // Finds the instruction where the map is being initialized in the constructor + val getHeaderIndex = indexOfFirstInstructionOrThrow { + getReference().let { + it?.name == mainFeedRequestHeaderFieldName + } + } + + val paramHeaderRegister = getInstruction(getHeaderIndex).registerA + + // Replace the `pagination_source` header value with `following` in the feed/timeline request. + addInstructions( + getHeaderIndex, + """ + invoke-static { v$paramHeaderRegister }, $EXTENSION_CLASS_DESCRIPTOR->setFollowingHeader(Ljava/util/Map;)Ljava/util/Map; + move-result-object v$paramHeaderRegister + """ + ) + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/navigation/HideNavigationButtons.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/navigation/HideNavigationButtons.kt index ee68c4fe1..cac9c35c3 100644 --- a/patches/src/main/kotlin/app/revanced/patches/instagram/hide/navigation/HideNavigationButtons.kt +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/hide/navigation/HideNavigationButtons.kt @@ -49,7 +49,7 @@ val hideNavigationButtonsPatch = bytecodePatch( val freeRegister = findFreeRegister(insertIndex, loopIndexRegister) val instruction = getInstruction(endIndex - 1) - var instructions = buildString { + val instructions = buildString { if (hideCreate!!) { appendLine( """ diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/SharedExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/SharedExtensionPatch.kt new file mode 100644 index 000000000..351b641a4 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/SharedExtensionPatch.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.instagram.misc.extension + +import app.revanced.patches.instagram.misc.extension.hooks.applicationInitHook +import app.revanced.patches.shared.misc.extension.sharedExtensionPatch + +val sharedExtensionPatch = sharedExtensionPatch( + "instagram", + applicationInitHook, +) diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/hooks/ApplicationInitHook.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/hooks/ApplicationInitHook.kt new file mode 100644 index 000000000..eca0a885f --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/extension/hooks/ApplicationInitHook.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.instagram.misc.extension.hooks + +import app.revanced.patches.shared.misc.extension.extensionHook + +internal val applicationInitHook = extensionHook { + custom { method, classDef -> + method.name == "onCreate" && classDef.endsWith("/InstagramAppShell;") + } +}