mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat(core): mark snaps as seen
This commit is contained in:
@ -612,6 +612,7 @@
|
|||||||
"auto_download": "\u2B07\uFE0F Auto Download",
|
"auto_download": "\u2B07\uFE0F Auto Download",
|
||||||
"auto_save": "\uD83D\uDCAC Auto Save Messages",
|
"auto_save": "\uD83D\uDCAC Auto Save Messages",
|
||||||
"stealth": "\uD83D\uDC7B Stealth Mode",
|
"stealth": "\uD83D\uDC7B Stealth Mode",
|
||||||
|
"mark_as_seen": "\uD83D\uDC40 Mark Snaps as seen",
|
||||||
"conversation_info": "\uD83D\uDC64 Conversation Info",
|
"conversation_info": "\uD83D\uDC64 Conversation Info",
|
||||||
"e2e_encryption": "\uD83D\uDD12 Use E2E Encryption"
|
"e2e_encryption": "\uD83D\uDD12 Use E2E Encryption"
|
||||||
},
|
},
|
||||||
@ -710,6 +711,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"friend_menu_option": {
|
"friend_menu_option": {
|
||||||
|
"mark_as_seen": "Mark Snaps as seen",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
"stealth_mode": "Stealth Mode",
|
"stealth_mode": "Stealth Mode",
|
||||||
"auto_download_blacklist": "Auto Download Blacklist",
|
"auto_download_blacklist": "Auto Download Blacklist",
|
||||||
|
@ -19,7 +19,7 @@ class UserInterfaceTweaks : ConfigContainer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val friendFeedMenuButtons = multiple(
|
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 {
|
).apply {
|
||||||
set(mutableListOf("conversation_info", MessagingRuleType.STEALTH.key))
|
set(mutableListOf("conversation_info", MessagingRuleType.STEALTH.key))
|
||||||
}
|
}
|
||||||
|
@ -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.Feature
|
||||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
|
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.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.Hooker
|
import me.rhunk.snapenhance.core.util.hook.Hooker
|
||||||
import me.rhunk.snapenhance.core.util.hook.hook
|
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.getObjectField
|
||||||
|
import me.rhunk.snapenhance.core.util.ktx.getObjectFieldOrNull
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.ConversationManager
|
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.SnapUUID
|
||||||
|
|
||||||
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
||||||
var conversationManager: ConversationManager? = null
|
var conversationManager: ConversationManager? = null
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
||||||
var openedConversationUUID: SnapUUID? = null
|
var openedConversationUUID: SnapUUID? = null
|
||||||
private set
|
private set
|
||||||
var lastFetchConversationUserUUID: SnapUUID? = null
|
var lastFetchConversationUserUUID: SnapUUID? = null
|
||||||
@ -27,8 +29,10 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
|||||||
var lastFocusedMessageId: Long = -1
|
var lastFocusedMessageId: Long = -1
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
private val feedCachedSnapMessages = EvictingMap<String, List<Long>>(100)
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
Hooker.hookConstructor(context.classCache.conversationManager, HookStage.BEFORE) { param ->
|
context.classCache.conversationManager.hookConstructor(HookStage.BEFORE) { param ->
|
||||||
conversationManager = ConversationManager(context, param.thisObject())
|
conversationManager = ConversationManager(context, param.thisObject())
|
||||||
context.messagingBridge.triggerSessionStart()
|
context.messagingBridge.triggerSessionStart()
|
||||||
context.mainActivity?.takeIf { it.intent.getBooleanExtra(ReceiversConfig.MESSAGING_PREVIEW_EXTRA,false) }?.run {
|
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() {
|
override fun onActivityCreate() {
|
||||||
context.mappings.getMappedObjectNullable("FriendsFeedEventDispatcher").let { it as? Map<*, *> }?.let { mappings ->
|
context.mappings.getMappedObjectNullable("FriendsFeedEventDispatcher").let { it as? Map<*, *> }?.let { mappings ->
|
||||||
findClass(mappings["class"].toString()).hook("onItemLongPress", HookStage.BEFORE) { param ->
|
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 ->
|
context.mappings.getMappedClass("callbacks", "GetOneOnOneConversationIdsCallback").hook("onSuccess", HookStage.BEFORE) { param ->
|
||||||
val userIdToConversation = (param.arg<ArrayList<*>>(0))
|
val userIdToConversation = (param.arg<ArrayList<*>>(0))
|
||||||
.takeIf { it.isNotEmpty() }
|
.takeIf { it.isNotEmpty() }
|
||||||
@ -96,12 +115,12 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
|||||||
lastFocusedMessageId = event.messageId
|
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))
|
lastFetchConversationUserUUID = SnapUUID((param.arg(0) as Any))
|
||||||
lastFocusedMessageId = param.arg(1)
|
lastFocusedMessageId = param.arg(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
Hooker.hook(context.classCache.conversationManager, "sendTypingNotification", HookStage.BEFORE, {
|
context.classCache.conversationManager.hook("sendTypingNotification", HookStage.BEFORE, {
|
||||||
hideTypingNotification || stealthMode.canUseRule(openedConversationUUID.toString())
|
hideTypingNotification || stealthMode.canUseRule(openedConversationUUID.toString())
|
||||||
}) {
|
}) {
|
||||||
it.setResult(null)
|
it.setResult(null)
|
||||||
|
@ -9,9 +9,15 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.CompoundButton
|
import android.widget.CompoundButton
|
||||||
|
import android.widget.ProgressBar
|
||||||
import android.widget.Switch
|
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.ContentType
|
||||||
import me.rhunk.snapenhance.common.data.FriendLinkType
|
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.ConversationMessage
|
||||||
import me.rhunk.snapenhance.common.database.impl.FriendInfo
|
import me.rhunk.snapenhance.common.database.impl.FriendInfo
|
||||||
import me.rhunk.snapenhance.common.database.impl.UserConversationLink
|
import me.rhunk.snapenhance.common.database.impl.UserConversationLink
|
||||||
@ -29,6 +35,9 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
class FriendFeedInfoMenu : AbstractMenu() {
|
class FriendFeedInfoMenu : AbstractMenu() {
|
||||||
private fun getImageDrawable(url: String): Drawable {
|
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) {
|
private fun showPreview(userId: String?, conversationId: String) {
|
||||||
//query message
|
//query message
|
||||||
val messageLogger = context.feature(MessageLogger::class)
|
val messageLogger = context.feature(MessageLogger::class)
|
||||||
@ -253,5 +300,13 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
|||||||
{ ruleFeature.setState(conversationId, it) }
|
{ 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) }
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user