commit 8823093b30746348bbfb1310823822f9f738e931
parent c357825dc75efef756a2fa08996cd088f1c56784
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed,  8 Nov 2023 01:43:47 +0100

feat(core): mark snaps as seen

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 2++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Messaging.kt | 29++++++++++++++++++++++++-----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/FriendFeedInfoMenu.kt | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 82 insertions(+), 6 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -612,6 +612,7 @@ "auto_download": "\u2B07\uFE0F Auto Download", "auto_save": "\uD83D\uDCAC Auto Save Messages", "stealth": "\uD83D\uDC7B Stealth Mode", + "mark_as_seen": "\uD83D\uDC40 Mark Snaps as seen", "conversation_info": "\uD83D\uDC64 Conversation Info", "e2e_encryption": "\uD83D\uDD12 Use E2E Encryption" }, @@ -710,6 +711,7 @@ }, "friend_menu_option": { + "mark_as_seen": "Mark Snaps as seen", "preview": "Preview", "stealth_mode": "Stealth Mode", "auto_download_blacklist": "Auto Download Blacklist", diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/UserInterfaceTweaks.kt @@ -19,7 +19,7 @@ class UserInterfaceTweaks : ConfigContainer() { } val friendFeedMenuButtons = multiple( - "friend_feed_menu_buttons","conversation_info", *MessagingRuleType.entries.filter { it.showInFriendMenu }.map { it.key }.toTypedArray() + "friend_feed_menu_buttons","conversation_info", "mark_as_seen", *MessagingRuleType.entries.filter { it.showInFriendMenu }.map { it.key }.toTypedArray() ).apply { set(mutableListOf("conversation_info", MessagingRuleType.STEALTH.key)) } 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 @@ -5,18 +5,20 @@ import me.rhunk.snapenhance.core.event.events.impl.OnSnapInteractionEvent import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.impl.spying.StealthMode +import me.rhunk.snapenhance.core.util.EvictingMap import me.rhunk.snapenhance.core.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.Hooker import me.rhunk.snapenhance.core.util.hook.hook +import me.rhunk.snapenhance.core.util.hook.hookConstructor import me.rhunk.snapenhance.core.util.ktx.getObjectField +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 class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) { var conversationManager: ConversationManager? = null private set - - var openedConversationUUID: SnapUUID? = null private set var lastFetchConversationUserUUID: SnapUUID? = null @@ -27,8 +29,10 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C var lastFocusedMessageId: Long = -1 private set + private val feedCachedSnapMessages = EvictingMap<String, List<Long>>(100) + override fun init() { - Hooker.hookConstructor(context.classCache.conversationManager, HookStage.BEFORE) { param -> + context.classCache.conversationManager.hookConstructor(HookStage.BEFORE) { param -> conversationManager = ConversationManager(context, param.thisObject()) context.messagingBridge.triggerSessionStart() context.mainActivity?.takeIf { it.intent.getBooleanExtra(ReceiversConfig.MESSAGING_PREVIEW_EXTRA,false) }?.run { @@ -37,6 +41,8 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C } } + fun getFeedCachedMessageIds(conversationId: String) = feedCachedSnapMessages[conversationId] + override fun onActivityCreate() { context.mappings.getMappedObjectNullable("FriendsFeedEventDispatcher").let { it as? Map<*, *> }?.let { mappings -> findClass(mappings["class"].toString()).hook("onItemLongPress", HookStage.BEFORE) { param -> @@ -51,6 +57,19 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C } } + val myUserId = context.database.myUserId + + context.classCache.feedEntry.hookConstructor(HookStage.AFTER) { param -> + val instance = param.thisObject<Any>() + val interactionInfo = instance.getObjectFieldOrNull("mInteractionInfo") ?: return@hookConstructor + val messages = (interactionInfo.getObjectFieldOrNull("mMessages") as? List<*>)?.map { Message(it) } ?: return@hookConstructor + val conversationId = SnapUUID(instance.getObjectFieldOrNull("mConversationId") ?: return@hookConstructor).toString() + + feedCachedSnapMessages[conversationId] = messages.filter { msg -> + msg.messageMetadata?.seenBy?.none { it.toString() == myUserId } == true + }.sortedBy { it.orderKey }.mapNotNull { it.messageDescriptor?.messageId } + } + context.mappings.getMappedClass("callbacks", "GetOneOnOneConversationIdsCallback").hook("onSuccess", HookStage.BEFORE) { param -> val userIdToConversation = (param.arg<ArrayList<*>>(0)) .takeIf { it.isNotEmpty() } @@ -96,12 +115,12 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C lastFocusedMessageId = event.messageId } - Hooker.hook(context.classCache.conversationManager, "fetchMessage", HookStage.BEFORE) { param -> + context.classCache.conversationManager.hook("fetchMessage", HookStage.BEFORE) { param -> lastFetchConversationUserUUID = SnapUUID((param.arg(0) as Any)) lastFocusedMessageId = param.arg(1) } - Hooker.hook(context.classCache.conversationManager, "sendTypingNotification", HookStage.BEFORE, { + context.classCache.conversationManager.hook("sendTypingNotification", HookStage.BEFORE, { hideTypingNotification || stealthMode.canUseRule(openedConversationUUID.toString()) }) { it.setResult(null) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/FriendFeedInfoMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/FriendFeedInfoMenu.kt @@ -9,9 +9,15 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.CompoundButton +import android.widget.ProgressBar import android.widget.Switch +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.data.FriendLinkType +import me.rhunk.snapenhance.common.data.MessageUpdate import me.rhunk.snapenhance.common.database.impl.ConversationMessage import me.rhunk.snapenhance.common.database.impl.FriendInfo import me.rhunk.snapenhance.common.database.impl.UserConversationLink @@ -29,6 +35,9 @@ import java.text.SimpleDateFormat import java.util.Calendar import java.util.Date import java.util.Locale +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlin.random.Random class FriendFeedInfoMenu : AbstractMenu() { private fun getImageDrawable(url: String): Drawable { @@ -101,6 +110,44 @@ class FriendFeedInfoMenu : AbstractMenu() { } } + private fun markAsSeen(conversationId: String) { + val messaging = context.feature(Messaging::class) + val messageIds = messaging.getFeedCachedMessageIds(conversationId)?.takeIf { it.isNotEmpty() } ?: run { + context.shortToast("No recent snaps found") + return + } + + var job: Job? = null + val dialog = ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity) + .setTitle("Processing...") + .setView(ProgressBar(context.mainActivity).apply { + setPadding(10, 10, 10, 10) + }) + .setOnDismissListener { job?.cancel() } + .show() + + context.coroutineScope.launch(Dispatchers.IO) { + messageIds.forEach { messageId -> + suspendCoroutine { continuation -> + messaging.conversationManager?.updateMessage(conversationId, messageId, MessageUpdate.READ) { + continuation.resume(Unit) + if (it != null && it != "DUPLICATEREQUEST") { + context.log.error("Error marking message as read $it") + } + } + } + delay(Random.nextLong(20, 60)) + context.runOnUiThread { + dialog.setTitle("Processing... (${messageIds.indexOf(messageId) + 1}/${messageIds.size})") + } + } + }.also { job = it }.invokeOnCompletion { + context.runOnUiThread { + dialog.dismiss() + } + } + } + private fun showPreview(userId: String?, conversationId: String) { //query message val messageLogger = context.feature(MessageLogger::class) @@ -253,5 +300,13 @@ class FriendFeedInfoMenu : AbstractMenu() { { ruleFeature.setState(conversationId, it) } ) } + + if (friendFeedMenuOptions.contains("mark_as_seen")) { + viewConsumer(Button(view.context).apply { + text = modContext.translation["friend_menu_option.mark_as_seen"] + applyTheme(view.width, hasRadius = true) + setOnClickListener { markAsSeen(conversationId) } + }) + } } } \ No newline at end of file