From 08e8ead04ffff47a4608a3db7aadc8d5feccd4ad Mon Sep 17 00:00:00 2001 From: Swakshan <56347042+Swakshan@users.noreply.github.com> Date: Wed, 1 Oct 2025 11:47:19 +0530 Subject: [PATCH] feat(Instagram): Add `Open links externally` patch (#6012) --- .../misc/links/OpenLinksExternallyPatch.java | 30 ++++++++++++ .../app/revanced/extension/shared/Utils.java | 13 +++++ extensions/twitter/build.gradle.kts | 4 +- .../links/ChangeLinkSharingDomainPatch.java | 7 +++ .../links/OpenLinksWithAppChooserPatch.java | 10 +++- patches/api/patches.api | 4 ++ .../instagram/misc/links/Fingerprint.kt | 9 ++++ .../misc/links/OpenLinksExternallyPatch.kt | 47 +++++++++++++++++++ .../twitter/misc/extension/ExtensionPatch.kt | 3 +- .../extension/hooks/ApplicationInitHook.kt | 10 ++++ .../links/ChangeLinkSharingDomainPatch.kt | 36 +++++++------- .../links/OpenLinksWithAppChooserPatch.kt | 4 +- 12 files changed, 153 insertions(+), 24 deletions(-) create mode 100644 extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch.java create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/misc/links/Fingerprint.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/instagram/misc/links/OpenLinksExternallyPatch.kt create mode 100644 patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/hooks/ApplicationInitHook.kt diff --git a/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch.java b/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch.java new file mode 100644 index 000000000..49db896c2 --- /dev/null +++ b/extensions/instagram/src/main/java/app/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch.java @@ -0,0 +1,30 @@ +package app.revanced.extension.instagram.misc.links; + +import android.net.Uri; + +import app.revanced.extension.shared.Logger; +import app.revanced.extension.shared.Utils; + +@SuppressWarnings("unused") +public final class OpenLinksExternallyPatch { + + /** + * Injection point. + */ + public static boolean openExternally(String url) { + try { + // The "url" parameter to this function will be of the form. + // https://l.instagram.com/?u=&e= + String actualUrl = Uri.parse(url).getQueryParameter("u"); + if (actualUrl != null) { + Utils.openLink(actualUrl); + return true; + } + + } catch (Exception ex) { + Logger.printException(() -> "openExternally failure", ex); + } + + return false; + } +} diff --git a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java index feeb379a7..a9d7b6e9d 100644 --- a/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java +++ b/extensions/shared/library/src/main/java/app/revanced/extension/shared/Utils.java @@ -15,6 +15,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Color; import android.net.ConnectivityManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -696,6 +697,18 @@ public class Utils { } } + public static void openLink(String url) { + try { + Intent intent = new Intent("android.intent.action.VIEW", Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + Logger.printInfo(() -> "Opening link with external browser: " + intent); + getContext().startActivity(intent); + } catch (Exception ex) { + Logger.printException(() -> "openLink failure", ex); + } + } + public enum NetworkType { NONE, MOBILE, diff --git a/extensions/twitter/build.gradle.kts b/extensions/twitter/build.gradle.kts index f3c06ad73..8cf6305c1 100644 --- a/extensions/twitter/build.gradle.kts +++ b/extensions/twitter/build.gradle.kts @@ -1 +1,3 @@ -// Do not remove. Necessary for the extension plugin to be applied to the project. +dependencies { + compileOnly(project(":extensions:shared:library")) +} diff --git a/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/ChangeLinkSharingDomainPatch.java b/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/ChangeLinkSharingDomainPatch.java index ff67394c6..f4f4a107d 100644 --- a/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/ChangeLinkSharingDomainPatch.java +++ b/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/ChangeLinkSharingDomainPatch.java @@ -1,15 +1,22 @@ package app.revanced.twitter.patches.links; +@SuppressWarnings("unused") public final class ChangeLinkSharingDomainPatch { private static final String DOMAIN_NAME = "https://fxtwitter.com"; private static final String LINK_FORMAT = "%s/%s/status/%s"; + /** + * Injection point. + */ public static String formatResourceLink(Object... formatArgs) { String username = (String) formatArgs[0]; String tweetId = (String) formatArgs[1]; return String.format(LINK_FORMAT, DOMAIN_NAME, username, tweetId); } + /** + * Injection point. + */ public static String formatLink(long tweetId, String username) { return String.format(LINK_FORMAT, DOMAIN_NAME, username, tweetId); } diff --git a/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/OpenLinksWithAppChooserPatch.java b/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/OpenLinksWithAppChooserPatch.java index 3a4dff608..bd0476d8b 100644 --- a/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/OpenLinksWithAppChooserPatch.java +++ b/extensions/twitter/src/main/java/app/revanced/twitter/patches/links/OpenLinksWithAppChooserPatch.java @@ -2,12 +2,18 @@ package app.revanced.twitter.patches.links; import android.content.Context; import android.content.Intent; -import android.util.Log; +import app.revanced.extension.shared.Logger; + +@SuppressWarnings("unused") @Deprecated(forRemoval = true) public final class OpenLinksWithAppChooserPatch { + + /** + * Injection point. + */ public static void openWithChooser(final Context context, final Intent intent) { - Log.d("ReVanced", "Opening intent with chooser: " + intent); + Logger.printInfo(() -> "Opening intent with chooser: " + intent); intent.setAction("android.intent.action.VIEW"); diff --git a/patches/api/patches.api b/patches/api/patches.api index 38dc2b638..2e35481c9 100644 --- a/patches/api/patches.api +++ b/patches/api/patches.api @@ -284,6 +284,10 @@ public final class app/revanced/patches/instagram/misc/extension/SharedExtension public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/BytecodePatch; } +public final class app/revanced/patches/instagram/misc/links/OpenLinksExternallyPatchKt { + public static final fun getOpenLinksExternallyPatch ()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/misc/links/Fingerprint.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/links/Fingerprint.kt new file mode 100644 index 000000000..b33d5ace5 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/links/Fingerprint.kt @@ -0,0 +1,9 @@ +package app.revanced.patches.instagram.misc.links +import app.revanced.patcher.fingerprint + +internal const val TARGET_STRING = "Tracking.ARG_CLICK_SOURCE" + +internal val inAppBrowserFunctionFingerprint = fingerprint { + returns("Z") + strings("TrackingInfo.ARG_MODULE_NAME", TARGET_STRING) +} diff --git a/patches/src/main/kotlin/app/revanced/patches/instagram/misc/links/OpenLinksExternallyPatch.kt b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/links/OpenLinksExternallyPatch.kt new file mode 100644 index 000000000..19b43e27e --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/instagram/misc/links/OpenLinksExternallyPatch.kt @@ -0,0 +1,47 @@ +package app.revanced.patches.instagram.misc.links + +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.indexOfFirstInstructionOrThrow +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction + +private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/instagram/misc/links/OpenLinksExternallyPatch;" + +@Suppress("unused") +val openLinksExternallyPatch = bytecodePatch( + name = "Open links externally", + description = "Changes links to always open in your external browser, instead of the in-app browser.", + use = false, +) { + + dependsOn(sharedExtensionPatch) + + compatibleWith("com.instagram.android") + + execute { + inAppBrowserFunctionFingerprint.let { + val stringMatchIndex = it.stringMatches?.first { match -> match.string == TARGET_STRING }!!.index + + it.method.apply { + val urlResultObjIndex = indexOfFirstInstructionOrThrow( + stringMatchIndex, Opcode.MOVE_OBJECT_FROM16 + ) + + // Register that contains the url after moving from a higher register. + val urlRegister = getInstruction(urlResultObjIndex).registerA + + addInstructions( + urlResultObjIndex + 1, + """ + invoke-static { v$urlRegister }, $EXTENSION_CLASS_DESCRIPTOR->openExternally(Ljava/lang/String;)Z + move-result v$urlRegister + return v$urlRegister + """ + ) + } + } + } +} diff --git a/patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/ExtensionPatch.kt b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/ExtensionPatch.kt index c5c758245..8f9b5574f 100644 --- a/patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/ExtensionPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/ExtensionPatch.kt @@ -1,5 +1,6 @@ package app.revanced.patches.twitter.misc.extension import app.revanced.patches.shared.misc.extension.sharedExtensionPatch +import app.revanced.patches.twitter.misc.extension.hooks.applicationInitHook -val sharedExtensionPatch = sharedExtensionPatch("twitter") +val sharedExtensionPatch = sharedExtensionPatch("twitter", applicationInitHook) diff --git a/patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/hooks/ApplicationInitHook.kt b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/hooks/ApplicationInitHook.kt new file mode 100644 index 000000000..13a1590a7 --- /dev/null +++ b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/extension/hooks/ApplicationInitHook.kt @@ -0,0 +1,10 @@ +package app.revanced.patches.twitter.misc.extension.hooks + +import app.revanced.patches.shared.misc.extension.extensionHook + +internal val applicationInitHook = + extensionHook { + custom { method, classDef -> + classDef.type == "Lcom/twitter/app/TwitterApplication;" && method.name == "onCreate" + } + } \ No newline at end of file diff --git a/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt index 289a71e72..982fdb5a4 100644 --- a/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/ChangeLinkSharingDomainPatch.kt @@ -53,28 +53,28 @@ val changeLinkSharingDomainPatch = bytecodePatch( ) execute { - val replacementIndex = - linkSharingDomainFingerprint.stringMatches!!.first().index - val domainRegister = - linkSharingDomainFingerprint.method.getInstruction(replacementIndex).registerA + linkSharingDomainFingerprint.let { + val replacementIndex = it.stringMatches!!.first().index + val domainRegister = it.method.getInstruction( + replacementIndex + ).registerA - linkSharingDomainFingerprint.method.replaceInstruction( - replacementIndex, - "const-string v$domainRegister, \"https://$domainName\"", - ) - - // Replace the domain name when copying a link with "Copy link" button. - linkBuilderFingerprint.method.apply { - addInstructions( - 0, - """ - invoke-static { p0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->formatLink(JLjava/lang/String;)Ljava/lang/String; - move-result-object p0 - return-object p0 - """, + it.method.replaceInstruction( + replacementIndex, + "const-string v$domainRegister, \"https://$domainName\"", ) } + // Replace the domain name when copying a link with "Copy link" button. + linkBuilderFingerprint.method.addInstructions( + 0, + """ + invoke-static { p0, p1, p2 }, $EXTENSION_CLASS_DESCRIPTOR->formatLink(JLjava/lang/String;)Ljava/lang/String; + move-result-object p0 + return-object p0 + """ + ) + // Used in the Share via... dialog. linkResourceGetterFingerprint.method.apply { val templateIdConstIndex = indexOfFirstLiteralInstructionOrThrow(tweetShareLinkTemplateId) diff --git a/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/OpenLinksWithAppChooserPatch.kt b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/OpenLinksWithAppChooserPatch.kt index 268f5789f..9c109e360 100644 --- a/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/OpenLinksWithAppChooserPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/twitter/misc/links/OpenLinksWithAppChooserPatch.kt @@ -9,7 +9,7 @@ import app.revanced.patches.twitter.misc.extension.sharedExtensionPatch @Suppress("unused") val openLinksWithAppChooserPatch = bytecodePatch( description = "Instead of opening links directly, open them with an app chooser. " + - "As a result you can select a browser to open the link with.", + "As a result you can select a browser to open the link with.", ) { dependsOn(sharedExtensionPatch) @@ -18,7 +18,7 @@ val openLinksWithAppChooserPatch = bytecodePatch( execute { val methodReference = "Lapp/revanced/extension/twitter/patches/links/OpenLinksWithAppChooserPatch;->" + - "openWithChooser(Landroid/content/Context;Landroid/content/Intent;)V" + "openWithChooser(Landroid/content/Context;Landroid/content/Intent;)V" openLinkFingerprint.method.addInstructions( 0,