commit b378bdde871f28fb7408c2c99e01cf195f876937
parent 7aa05e996a3e0119c6c8cbfe41718551a5bde1ba
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Thu, 28 Dec 2023 00:42:01 +0100

feat(core): spotlight comments username

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 4++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Global.kt | 1+
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Messaging.kt | 21+++++++++++++++++++++
Acore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/SpotlightCommentsUsername.kt | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt | 1+
Acore/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/Snapchatter.kt | 20++++++++++++++++++++
6 files changed, 102 insertions(+), 0 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -497,6 +497,10 @@ "name": "Block Ads", "description": "Prevents Advertisements from being displayed" }, + "spotlight_comments_username": { + "name": "Spotlight Comments Username", + "description": "Shows author username in Spotlight comments" + }, "bypass_video_length_restriction": { "name": "Bypass Video Length Restrictions", "description": "Single: sends a single video\nSplit: split videos after editing" diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Global.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Global.kt @@ -14,6 +14,7 @@ class Global : ConfigContainer() { val disableMetrics = boolean("disable_metrics") val disablePublicStories = boolean("disable_public_stories") { requireRestart(); requireCleanCache() } val blockAds = boolean("block_ads") + val spotlightCommentsUsername = boolean("spotlight_comments_username") { requireRestart() } val bypassVideoLengthRestriction = unique("bypass_video_length_restriction", "split", "single") { addNotices( FeatureNotice.BAN_RISK); requireRestart(); nativeHooks() } val disableGooglePlayDialogs = boolean("disable_google_play_dialogs") { requireRestart() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Messaging.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Messaging.kt @@ -16,12 +16,16 @@ import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull import me.rhunk.snapenhance.core.wrapper.impl.ConversationManager import me.rhunk.snapenhance.core.wrapper.impl.Message import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID +import me.rhunk.snapenhance.core.wrapper.impl.Snapchatter import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID +import java.util.concurrent.Future class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) { var conversationManager: ConversationManager? = null private set private var conversationManagerDelegate: Any? = null + private var identityDelegate: Any? = null + var openedConversationUUID: SnapUUID? = null private set var lastFetchConversationUserUUID: SnapUUID? = null @@ -57,6 +61,12 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C } } } + + context.mappings.getMappedClass("callbacks", "IdentityDelegate").apply { + hookConstructor(HookStage.AFTER) { + identityDelegate = it.thisObject() + } + } } fun getFeedCachedMessageIds(conversationId: String) = feedCachedSnapMessages[conversationId] @@ -169,4 +179,15 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C it.setResult(null) } } + + fun fetchSnapchatterInfos(userIds: List<String>): List<Snapchatter> { + val identity = identityDelegate ?: return emptyList() + val future = identity::class.java.methods.first { + it.name == "fetchSnapchatterInfos" + }.invoke(identity, userIds.map { + it.toSnapUUID().instanceNonNull() + }) as Future<*> + + return (future.get() as? List<*>)?.map { Snapchatter(it) } ?: return emptyList() + } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/SpotlightCommentsUsername.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/ui/SpotlightCommentsUsername.kt @@ -0,0 +1,54 @@ +package me.rhunk.snapenhance.core.features.impl.ui + +import android.annotation.SuppressLint +import android.widget.TextView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent +import me.rhunk.snapenhance.core.features.Feature +import me.rhunk.snapenhance.core.features.FeatureLoadParams +import me.rhunk.snapenhance.core.features.impl.messaging.Messaging +import me.rhunk.snapenhance.core.util.EvictingMap +import me.rhunk.snapenhance.core.util.ktx.getId + +class SpotlightCommentsUsername : Feature("SpotlightCommentsUsername", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { + private val usernameCache = EvictingMap<String, String>(150) + + @SuppressLint("SetTextI18n") + override fun onActivityCreate() { + if (!context.config.global.spotlightCommentsUsername.get()) return + + val messaging = context.feature(Messaging::class) + val commentsCreatorBadgeTimestampId = context.resources.getId("comments_creator_badge_timestamp") + + context.event.subscribe(BindViewEvent::class) { event -> + val commentsCreatorBadgeTimestamp = event.view.findViewById<TextView>(commentsCreatorBadgeTimestampId) ?: return@subscribe + + val posterUserId = event.prevModel.toString().takeIf { it.startsWith("Comment") } + ?.substringAfter("posterUserId=")?.substringBefore(",")?.substringBefore(")") ?: return@subscribe + + fun setUsername(username: String) { + usernameCache[posterUserId] = username + commentsCreatorBadgeTimestamp.text = " (${username})" + commentsCreatorBadgeTimestamp.text.toString() + } + + usernameCache[posterUserId]?.let { + setUsername(it) + return@subscribe + } + + context.coroutineScope.launch { + val username = runCatching { + messaging.fetchSnapchatterInfos(listOf(posterUserId)).firstOrNull() + }.onFailure { + context.log.error("Failed to fetch snapchatter info for user $posterUserId", it) + }.getOrNull()?.username ?: return@launch + + withContext(Dispatchers.Main) { + setUsername(username) + } + } + } + } +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt @@ -119,6 +119,7 @@ class FeatureManager( PreventForcedLogout::class, SuspendLocationUpdates::class, ConversationToolbox::class, + SpotlightCommentsUsername::class, ) initializeFeatures() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/Snapchatter.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/Snapchatter.kt @@ -0,0 +1,19 @@ +package me.rhunk.snapenhance.core.wrapper.impl + +import me.rhunk.snapenhance.core.wrapper.AbstractWrapper + + + +class BitmojiInfo(obj: Any?) : AbstractWrapper(obj) { + var avatarId by field<String?>("mAvatarId") + var backgroundId by field<String?>("mBackgroundId") + var sceneId by field<String?>("mSceneId") + var selfieId by field<String?>("mSelfieId") +} + +class Snapchatter(obj: Any?) : AbstractWrapper(obj) { + val bitmojiInfo by field<BitmojiInfo?>("mBitmojiInfo") + var displayName by field<String?>("mDisplayName") + var userId by field("mUserId") { SnapUUID(it) } + var username by field<String>("mUsername") +}+ \ No newline at end of file