diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json index 79a4d63b..7287ae4c 100644 --- 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 index c5f7143b..c74d0033 100644 --- 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 index ef495bd7..19bee021 100644 --- 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): List { + 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 new file mode 100644 index 00000000..25892874 --- /dev/null +++ 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(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(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 index 266b0d7a..6e0df510 100644 --- 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 new file mode 100644 index 00000000..0a3dcca1 --- /dev/null +++ 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("mAvatarId") + var backgroundId by field("mBackgroundId") + var sceneId by field("mSceneId") + var selfieId by field("mSelfieId") +} + +class Snapchatter(obj: Any?) : AbstractWrapper(obj) { + val bitmojiInfo by field("mBitmojiInfo") + var displayName by field("mDisplayName") + var userId by field("mUserId") { SnapUUID(it) } + var username by field("mUsername") +} \ No newline at end of file