commit c7a2d0268796638661c54f724c8f08f1a2676a9d
parent 7ded784d8c850fbf98d59bd3fb5bb13b073a6c64
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sat,  8 Mar 2025 12:31:18 +0100

feat(core): snap score changes

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/RemoteTracker.kt | 5+++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/storage/AppDatabase.kt | 4++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/storage/Tracker.kt | 23+++++++++++++++++++++++
Mcommon/src/main/aidl/me/rhunk/snapenhance/bridge/logger/TrackerInterface.aidl | 2++
Mcommon/src/main/assets/lang/en_US.json | 4++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt | 1+
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt | 1+
Acore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/SnapScoreChanges.kt | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 109 insertions(+), 0 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteTracker.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteTracker.kt @@ -8,6 +8,7 @@ import me.rhunk.snapenhance.common.data.TrackerRuleEvent import me.rhunk.snapenhance.common.util.toSerialized import me.rhunk.snapenhance.storage.getRuleTrackerScopes import me.rhunk.snapenhance.storage.getTrackerEvents +import me.rhunk.snapenhance.storage.updateFriendScore class RemoteTracker( @@ -26,4 +27,8 @@ class RemoteTracker( ScopedTrackerRule(it.key, context.database.getRuleTrackerScopes(it.key.id)) }).toSerialized() } + + override fun updateFriendScore(userId: String, score: Long): Long { + return context.database.updateFriendScore(userId, score) + } } \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/storage/AppDatabase.kt b/app/src/main/kotlin/me/rhunk/snapenhance/storage/AppDatabase.kt @@ -78,6 +78,10 @@ class AppDatabase( "params TEXT", "actions TEXT" ), + "friend_scores" to listOf( + "userId CHAR(36) PRIMARY KEY", + "score BIGINT" + ), "quick_tiles" to listOf( "key VARCHAR PRIMARY KEY", "position INTEGER", diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/storage/Tracker.kt b/app/src/main/kotlin/me/rhunk/snapenhance/storage/Tracker.kt @@ -9,6 +9,7 @@ import me.rhunk.snapenhance.common.data.TrackerRuleActionParams import me.rhunk.snapenhance.common.data.TrackerRuleEvent import me.rhunk.snapenhance.common.data.TrackerScopeType import me.rhunk.snapenhance.common.util.ktx.getInteger +import me.rhunk.snapenhance.common.util.ktx.getLongOrNull import me.rhunk.snapenhance.common.util.ktx.getStringOrNull import kotlin.coroutines.suspendCoroutine @@ -195,3 +196,24 @@ fun AppDatabase.getRuleTrackerScopes(ruleId: Int, limit: Int = Int.MAX_VALUE): M } return scopes } + +fun AppDatabase.updateFriendScore(userId: String, score: Long): Long { + return runBlocking { + suspendCoroutine { continuation -> + executeAsync { + val currentScore = database.rawQuery("SELECT score FROM friend_scores WHERE userId = ?", arrayOf(userId)).use { cursor -> + if (!cursor.moveToFirst()) return@use null + cursor.getLongOrNull("score") + } + + if (currentScore != null) { + database.execSQL("UPDATE friend_scores SET score = ? WHERE userId = ?", arrayOf(score, userId)) + } else { + database.execSQL("INSERT INTO friend_scores (userId, score) VALUES (?, ?)", arrayOf(userId, score)) + } + + continuation.resumeWith(Result.success(currentScore ?: -1)) + } + } + } +}+ \ No newline at end of file diff --git a/common/src/main/aidl/me/rhunk/snapenhance/bridge/logger/TrackerInterface.aidl b/common/src/main/aidl/me/rhunk/snapenhance/bridge/logger/TrackerInterface.aidl @@ -2,4 +2,6 @@ package me.rhunk.snapenhance.bridge.logger; interface TrackerInterface { String getTrackedEvents(String eventType); // returns serialized TrackerEventsResult + + long updateFriendScore(String userId, long score); // returns old score (-1 if not found) } \ No newline at end of file diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -1152,6 +1152,10 @@ "prevent_forced_logout": { "name": "Prevent Forced Logout", "description": "Prevents Snapchat from logging you out when you login on another device" + }, + "snapscore_changes": { + "name": "Snapscore Changes", + "description": "Tracks changes in friends Snapscore\nUse this feature in newer versions of Snapchat only" } } }, diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt @@ -94,4 +94,5 @@ class Experimental : ConfigContainer() { "added_by_quick_add", ) { addNotices(FeatureNotice.BAN_RISK) } val preventForcedLogout = boolean("prevent_forced_logout") { requireRestart(); addNotices(FeatureNotice.BAN_RISK, FeatureNotice.INTERNAL_BEHAVIOR); } + val snapScoreChanges = boolean("snapscore_changes") { requireRestart() } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt @@ -142,6 +142,7 @@ class FeatureManager( VoiceNoteOverride(), FriendNotes(), DoubleTapChatAction(), + SnapScoreChanges(), ) features.values.toList().forEach { feature -> diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/SnapScoreChanges.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/SnapScoreChanges.kt @@ -0,0 +1,68 @@ +package me.rhunk.snapenhance.core.features.impl.experiments + +import android.view.ViewGroup +import me.rhunk.snapenhance.common.util.protobuf.ProtoReader +import me.rhunk.snapenhance.core.event.events.impl.AddViewEvent +import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent +import me.rhunk.snapenhance.core.features.Feature +import me.rhunk.snapenhance.core.ui.getComposerContext +import me.rhunk.snapenhance.core.ui.getComposerViewNode +import me.rhunk.snapenhance.core.util.ktx.getObjectField +import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID + +class SnapScoreChanges: Feature("Snap Score Changes") { + private val scores = mutableMapOf<String, Long>() + private var lastViewedUserId: String? = null + + override fun init() { + if (!context.config.experimental.snapScoreChanges.get()) return + + context.event.subscribe(UnaryCallEvent::class) { event -> + if (event.uri != "/com.snapchat.atlas.gw.AtlasGw/GetFriendsUserScore") return@subscribe + + event.addResponseCallback { + synchronized(scores) { + ProtoReader(buffer).eachBuffer(1) { + val friendUUID = getByteArray(1) ?: return@eachBuffer + val score = getVarInt(2) ?: return@eachBuffer + + scores[SnapUUID(friendUUID).toString()] = score + } + } + } + } + + context.event.subscribe(AddViewEvent::class) { event -> + if (event.viewClassName.endsWith("UnifiedProfileFlatlandProfileViewTopViewFrameLayout")) { + val composerView = (event.view as ViewGroup).getChildAt(0) ?: return@subscribe + val composerContext = composerView.getComposerContext() ?: return@subscribe + + lastViewedUserId = composerContext.viewModel?.getObjectField("_userId")?.toString() + } + + if (event.viewClassName.endsWith("ProfileFlatlandFriendSnapScoreIdentityPillDialogView")) { + event.view.post { + val composerViewNode = event.view.getComposerViewNode() ?: return@post + val surface = composerViewNode.getChildren().getOrNull(1) ?: return@post + + val snapTextView = surface.getChildren().lastOrNull { + it.getClassName() == "com.snap.composer.views.ComposerSnapTextView" + } ?: return@post + + + val currentFriendScore = scores[lastViewedUserId] ?: (event.view.getComposerContext()?.viewModel?.getObjectField("_friendSnapScore") as? Double)?.toLong() ?: return@post + + val oldSnapScore = context.bridgeClient.getTracker().updateFriendScore( + lastViewedUserId ?: return@post, + currentFriendScore + ) + + val diff = currentFriendScore - oldSnapScore + + snapTextView.setAttribute("value", "${if (oldSnapScore != -1L && diff > 0) "\uD83D\uDCC8 +$diff !\n\n" else ""}Last Checked Score: ${oldSnapScore.takeIf { it != -1L } ?: "N/A"}") + event.view.invalidate() + } + } + } + } +}+ \ No newline at end of file