Merge branch 'github/fork/Swakshan/Instagram/followback-indicator' into 'dev'
feat(Instagram): Add `Follow back indicator` patch See merge request ReVanced/revanced-patches!6564
This commit is contained in:
commit
3abbd1f6fc
9 changed files with 292 additions and 0 deletions
|
|
@ -1,5 +1,6 @@
|
|||
dependencies {
|
||||
compileOnly(project(":extensions:shared:library"))
|
||||
compileOnly(project(":extensions:instagram:stub"))
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
package app.revanced.extension.instagram.misc.followbackindicator;
|
||||
|
||||
import app.revanced.extension.shared.Logger;
|
||||
import app.revanced.extension.shared.Utils;
|
||||
import app.revanced.extension.instagram.misc.followbackindicator.Helper;
|
||||
import com.instagram.common.session.UserSession;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class FollowBackIndicatorPatch {
|
||||
|
||||
public static void indicator(UserSession userSession, Object profileInfoObject, Object badgeObject){
|
||||
try {
|
||||
String loggedInUserId = userSession.getUserId();
|
||||
Object viewingProfileUserObject = Helper.getViewingProfileUserObject(profileInfoObject);
|
||||
String viewingProfileUserId = Helper.getViewingProfileUserId(viewingProfileUserObject);
|
||||
|
||||
// If the logged in user id is same as viewing profile, then no need to display the badge.
|
||||
if(loggedInUserId.equals(viewingProfileUserId)) return;
|
||||
|
||||
Boolean followed_by = Helper.getFollowbackInfo(viewingProfileUserObject);
|
||||
String indicatorText = followed_by ? "Follows you" : "Does not follow you";
|
||||
Helper.setInternalBadgeText(badgeObject,indicatorText);
|
||||
|
||||
} catch (Exception ex){
|
||||
Logger.printException(() -> "Failed follow back indicator", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package app.revanced.extension.instagram.misc.followbackindicator;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import android.widget.TextView;
|
||||
import android.view.View;
|
||||
import java.lang.reflect.Field;
|
||||
@SuppressWarnings("unused")
|
||||
public class Helper {
|
||||
|
||||
/**
|
||||
* Given method name and class object, this function invokes
|
||||
* the method on the object by bypassing access restrictions.
|
||||
*
|
||||
* @param clsObj The object on which to invoke the method.
|
||||
* @param methodName The name of the method to invoke.
|
||||
* @return The return value of the invoked method as Object.
|
||||
* @throws Exception If an exception occurs during the method invocation.
|
||||
**/
|
||||
private static Object invokeMethod(Object clsObj, String methodName) throws Exception {
|
||||
return clsObj.getClass().getDeclaredMethod(methodName).invoke(clsObj);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given profile info object, this function return user object.
|
||||
*
|
||||
* @param classObject profile info object.
|
||||
* @return The user data as Object.
|
||||
* @throws Exception If an exception occurs during the method invocation.
|
||||
**/
|
||||
public static Object getViewingProfileUserObject(Object classObject)throws Exception{
|
||||
Class<?> clazz = classObject.getClass();
|
||||
Field field = clazz.getDeclaredField("FieldName");
|
||||
field.setAccessible(true);
|
||||
return field.get(classObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given user object, this function returns if an user
|
||||
* is following the logged it user or not.
|
||||
*
|
||||
* @param userObject The viewing profile user object.
|
||||
* @return The boolean follow back value.
|
||||
* @throws Exception If an exception occurs during the method invocation.
|
||||
**/
|
||||
public static Boolean getFollowbackInfo(Object userObject) throws Exception {
|
||||
Class<?> clazz = Class.forName("className");
|
||||
Method method = clazz.getDeclaredMethod("methodName", userObject.getClass());
|
||||
method.setAccessible(true);
|
||||
Object result = method.invoke(null, userObject);
|
||||
return (Boolean) result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given user object, this function returns user's id.
|
||||
*
|
||||
* @param userObject The viewing profile user object.
|
||||
* @return The user ID as string.
|
||||
* @throws Exception If an exception occurs during the method invocation.
|
||||
**/
|
||||
public static String getViewingProfileUserId(Object userObject) throws Exception {
|
||||
return (String) Helper.invokeMethod(userObject, "getId");
|
||||
}
|
||||
|
||||
/**
|
||||
* Given badge object and text, this function,
|
||||
* sets text to the badge and makes it visible.
|
||||
*
|
||||
* @param badgeObject The viewing profile user object.
|
||||
* @param text String text to set in badge label.
|
||||
* @throws Exception If an exception occurs during the method invocation.
|
||||
**/
|
||||
public static void setInternalBadgeText(Object badgeObject,String text) throws Exception {
|
||||
TextView badgeView = (TextView) Helper.invokeMethod(badgeObject,"getView");
|
||||
badgeView.setVisibility(View.VISIBLE);
|
||||
badgeView.setText(text);
|
||||
}
|
||||
}
|
||||
17
extensions/instagram/stub/build.gradle.kts
Normal file
17
extensions/instagram/stub/build.gradle.kts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
plugins {
|
||||
alias(libs.plugins.android.library)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.revanced.extension"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
1
extensions/instagram/stub/src/main/AndroidManifest.xml
Normal file
1
extensions/instagram/stub/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1 @@
|
|||
<manifest/>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.instagram.common.session;
|
||||
|
||||
public class UserSession {
|
||||
public String getUserId() {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -345,6 +345,10 @@ public final class app/revanced/patches/instagram/misc/extension/SharedExtension
|
|||
public static final fun getSharedExtensionPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/misc/followBackIndicator/FollowBackIndicatorPatchKt {
|
||||
public static final fun getFollowBackIndicatorPatch ()Lapp/revanced/patcher/patch/BytecodePatch;
|
||||
}
|
||||
|
||||
public final class app/revanced/patches/instagram/misc/links/OpenLinksExternallyPatchKt {
|
||||
public static final fun getOpenLinksExternallyPatch ()Lapp/revanced/patcher/patch/Patch;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package app.revanced.patches.instagram.misc.followBackIndicator
|
||||
import app.revanced.patcher.fingerprint
|
||||
import com.android.tools.smali.dexlib2.AccessFlags
|
||||
|
||||
internal const val INTERNAL_BADGE_TARGET_STRING = "bindInternalBadges"
|
||||
internal const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/instagram/misc/followbackindicator/"
|
||||
internal const val EXTENSION_HELPER_CLASS_DESCRIPTOR = "${EXTENSION_CLASS_DESCRIPTOR}Helper;"
|
||||
|
||||
internal val bindInternalBadgeFingerprint = fingerprint {
|
||||
strings(INTERNAL_BADGE_TARGET_STRING)
|
||||
}
|
||||
|
||||
internal val bindRowViewTypesFingerprint = fingerprint {
|
||||
strings("NONE should not map to item type")
|
||||
}
|
||||
|
||||
internal val nametagResultCardViewSetButtonMethodFingerprint = fingerprint {
|
||||
returns("V")
|
||||
accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL)
|
||||
custom { method, classDef ->
|
||||
classDef.type.endsWith("NametagResultCardView;") && method.parameters.size == 3
|
||||
}
|
||||
}
|
||||
|
||||
internal val getFollowbackInfoExtensionFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "getFollowbackInfo" && classDef.type == EXTENSION_HELPER_CLASS_DESCRIPTOR
|
||||
}
|
||||
}
|
||||
|
||||
internal val getViewingProfileUserObjectExtensionFingerprint = fingerprint {
|
||||
custom { method, classDef ->
|
||||
method.name == "getViewingProfileUserObject" && classDef.type == EXTENSION_HELPER_CLASS_DESCRIPTOR
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,121 @@
|
|||
package app.revanced.patches.instagram.misc.followBackIndicator
|
||||
|
||||
import app.revanced.patcher.Fingerprint
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.instructions
|
||||
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
|
||||
import app.revanced.patcher.patch.bytecodePatch
|
||||
import app.revanced.patcher.util.smali.ExternalLabel
|
||||
import app.revanced.patches.instagram.misc.extension.sharedExtensionPatch
|
||||
import app.revanced.util.getReference
|
||||
import app.revanced.util.indexOfFirstInstruction
|
||||
import com.android.tools.smali.dexlib2.Opcode
|
||||
import com.android.tools.smali.dexlib2.builder.instruction.BuilderInstruction21c
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
|
||||
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
|
||||
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
|
||||
|
||||
@Suppress("unused")
|
||||
val followBackIndicatorPatch = bytecodePatch(
|
||||
name = "Follow back indicator",
|
||||
description = "Adds a label in profile page, indicating if an user is follows you back.",
|
||||
use = true,
|
||||
) {
|
||||
dependsOn(sharedExtensionPatch)
|
||||
|
||||
compatibleWith("com.instagram.android")
|
||||
|
||||
execute {
|
||||
/**
|
||||
* This function replaces a string instruction with a new one.
|
||||
*
|
||||
* @param index The index of the string constant.
|
||||
* @param value The new string to be replaced with.
|
||||
*/
|
||||
fun Fingerprint.changeString(
|
||||
index: Int,
|
||||
value: String,
|
||||
) {
|
||||
method.instructions.filter { it.opcode == Opcode.CONST_STRING }[index].let { instruction ->
|
||||
val register = (instruction as BuilderInstruction21c).registerA
|
||||
method.replaceInstruction(instruction.location.index, "const-string v$register, \"$value\"")
|
||||
}
|
||||
}
|
||||
|
||||
// This fingerprint is used to identify the static method and its defining class,
|
||||
// which is used for identifying a user's follow back status.
|
||||
nametagResultCardViewSetButtonMethodFingerprint.method.apply {
|
||||
val moveResultIndex = instructions.first { it.opcode == Opcode.MOVE_RESULT }.location.index
|
||||
val invokeStaticMethodReference = getInstruction(moveResultIndex - 1).getReference<MethodReference>()
|
||||
|
||||
val methodDefClassName = invokeStaticMethodReference!!.definingClass.removePrefix("L").replace("/", ".").removeSuffix(";")
|
||||
getFollowbackInfoExtensionFingerprint.changeString(0,methodDefClassName)
|
||||
|
||||
val methodName = invokeStaticMethodReference.name
|
||||
getFollowbackInfoExtensionFingerprint.changeString(1,methodName)
|
||||
}
|
||||
|
||||
// This constant stores the value of the obfuscated profile info class,
|
||||
// which is later used to find the index of the parameter.
|
||||
var profileInfoClassName:String
|
||||
|
||||
// This fingerprint is used to identify field name in obfuscated profile info class,
|
||||
// that holds user data.
|
||||
bindRowViewTypesFingerprint.method.apply {
|
||||
val igetObjectInstruction = instructions.first { it.opcode == Opcode.IGET_OBJECT }
|
||||
val fieldReference = igetObjectInstruction.getReference<FieldReference>()
|
||||
val userObjectFieldName = fieldReference!!.name
|
||||
getViewingProfileUserObjectExtensionFingerprint.changeString(0,userObjectFieldName)
|
||||
profileInfoClassName = fieldReference.definingClass
|
||||
}
|
||||
|
||||
// This fingerprint is used to identify the internal badge, which is used for displaying follow back status.
|
||||
bindInternalBadgeFingerprint.method.apply {
|
||||
val internalBadgeStringIndex = bindInternalBadgeFingerprint.stringMatches!![0].index
|
||||
|
||||
// Identify the profile info in the method parameter, which is later passed to our custom hook.
|
||||
val profileInfoParameter = parameters.indexOfFirst { it.type == profileInfoClassName }
|
||||
|
||||
val internalBadgeInstructionIndex = indexOfFirstInstruction(internalBadgeStringIndex, Opcode.IGET_OBJECT)
|
||||
val internalBadgeInstruction = getInstruction<TwoRegisterInstruction>(internalBadgeInstructionIndex)
|
||||
// Internal badge is an element/view, which is used internally to mark developers.
|
||||
// We hook and update its text to display the follow back status.
|
||||
val internalBadgeRegistry = internalBadgeInstruction.registerA
|
||||
// User profile page (obfuscated) contains all the elements that are present on the user page.
|
||||
// We are hooking it in order to find user session, which is used to get info on logged in user.
|
||||
val userProfilePageRegistry = internalBadgeInstruction.registerB
|
||||
|
||||
// Finding the necessary dummy registries.
|
||||
val dummyRegistryInstructionIndex = indexOfFirstInstruction(internalBadgeInstructionIndex + 1, Opcode.IGET_OBJECT)
|
||||
val dummyRegistry1 = getInstruction<TwoRegisterInstruction>(dummyRegistryInstructionIndex).registerA
|
||||
val dummyRegistry2 = getInstruction<OneRegisterInstruction>(internalBadgeStringIndex).registerA
|
||||
|
||||
// Instruction to which the call needs to transfer after our hook.
|
||||
val invokeStaticRangeIndex = indexOfFirstInstruction(internalBadgeInstructionIndex, Opcode.INVOKE_STATIC_RANGE)
|
||||
|
||||
val userSessionClassName = "Lcom/instagram/common/session/UserSession;"
|
||||
// Finding the user profile page (obfuscated) class name.
|
||||
val userProfilePageElementsClassName = internalBadgeInstruction.getReference<FieldReference>()!!.definingClass
|
||||
// Finding the user session field.
|
||||
val userSessionFieldName = classes.find { it.type == userProfilePageElementsClassName }!!.fields.first { it.type == userSessionClassName }.name
|
||||
|
||||
// Added instructions:
|
||||
// Get the user session.
|
||||
// Move the profile info parameter to a suitable registry.
|
||||
// Call our hook, which will update the badge.
|
||||
addInstructionsWithLabels(
|
||||
internalBadgeInstructionIndex + 1,
|
||||
"""
|
||||
iget-object v$dummyRegistry1, v$userProfilePageRegistry, $userProfilePageElementsClassName->$userSessionFieldName:$userSessionClassName
|
||||
move-object/from16 v$dummyRegistry2, p$profileInfoParameter
|
||||
|
||||
invoke-static {v$dummyRegistry1,v$dummyRegistry2, v$internalBadgeRegistry}, ${EXTENSION_CLASS_DESCRIPTOR}FollowBackIndicatorPatch;->indicator($userSessionClassName Ljava/lang/Object;Ljava/lang/Object;)V
|
||||
goto :revanced
|
||||
""".trimIndent(),
|
||||
ExternalLabel("revanced", getInstruction(invokeStaticRangeIndex)),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue