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:
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