feat(core/events): conversation updated event

This commit is contained in:
rhunk 2023-12-12 23:37:02 +01:00
parent 37daae3799
commit d0ff3c35ef
5 changed files with 56 additions and 60 deletions

View File

@ -0,0 +1,10 @@
package me.rhunk.snapenhance.core.event.events.impl
import me.rhunk.snapenhance.core.event.events.AbstractHookEvent
import me.rhunk.snapenhance.core.wrapper.impl.Message
class ConversationUpdateEvent(
val conversationId: String,
val conversation: Any?,
val messages: List<Message>
) : AbstractHookEvent()

View File

@ -3,13 +3,13 @@ package me.rhunk.snapenhance.core.features.impl.messaging
import me.rhunk.snapenhance.common.data.MessageState import me.rhunk.snapenhance.common.data.MessageState
import me.rhunk.snapenhance.common.data.MessageUpdate import me.rhunk.snapenhance.common.data.MessageUpdate
import me.rhunk.snapenhance.common.data.MessagingRuleType import me.rhunk.snapenhance.common.data.MessagingRuleType
import me.rhunk.snapenhance.core.event.events.impl.ConversationUpdateEvent
import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.FeatureLoadParams
import me.rhunk.snapenhance.core.features.MessagingRuleFeature import me.rhunk.snapenhance.core.features.MessagingRuleFeature
import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
import me.rhunk.snapenhance.core.logger.CoreLogger
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.hook
import me.rhunk.snapenhance.core.util.ktx.getObjectField import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.wrapper.impl.Message import me.rhunk.snapenhance.core.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
@ -19,20 +19,18 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
private val asyncSaveExecutorService = Executors.newSingleThreadExecutor() private val asyncSaveExecutorService = Executors.newSingleThreadExecutor()
private val messageLogger by lazy { context.feature(MessageLogger::class) } private val messageLogger by lazy { context.feature(MessageLogger::class) }
private val messaging by lazy { context.feature(Messaging::class) }
private val autoSaveFilter by lazy { private val autoSaveFilter by lazy {
context.config.messaging.autoSaveMessagesInConversations.get() context.config.messaging.autoSaveMessagesInConversations.get()
} }
fun saveMessage(conversationId: SnapUUID, message: Message) { fun saveMessage(conversationId: String, message: Message) {
val messageId = message.messageDescriptor!!.messageId!! val messageId = message.messageDescriptor!!.messageId!!
if (messageLogger.takeIf { it.isEnabled }?.isMessageDeleted(conversationId.toString(), messageId) == true) return if (messageLogger.takeIf { it.isEnabled }?.isMessageDeleted(conversationId, messageId) == true) return
if (message.messageState != MessageState.COMMITTED) return
runCatching { runCatching {
context.feature(Messaging::class).conversationManager?.updateMessage( context.feature(Messaging::class).conversationManager?.updateMessage(
conversationId.toString(), conversationId,
messageId, messageId,
MessageUpdate.SAVE MessageUpdate.SAVE
) { ) {
@ -49,6 +47,8 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
} }
fun canSaveMessage(message: Message, headless: Boolean = false): Boolean { fun canSaveMessage(message: Message, headless: Boolean = false): Boolean {
if (message.messageState != MessageState.COMMITTED) return false
if (!headless && (context.mainActivity == null || context.isMainActivityPaused)) return false if (!headless && (context.mainActivity == null || context.isMainActivityPaused)) return false
if (message.messageMetadata!!.savedBy!!.any { uuid -> uuid.toString() == context.database.myUserId }) return false if (message.messageMetadata!!.savedBy!!.any { uuid -> uuid.toString() == context.database.myUserId }) return false
val contentType = message.messageContent!!.contentType.toString() val contentType = message.messageContent!!.contentType.toString()
@ -69,9 +69,8 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
} }
override fun asyncOnActivityCreate() { override fun asyncOnActivityCreate() {
//called when enter in a conversation (or when a message is sent) // called when enter in a conversation
Hooker.hook( context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback").hook(
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"),
"onFetchConversationWithMessagesComplete", "onFetchConversationWithMessagesComplete",
HookStage.BEFORE, HookStage.BEFORE,
{ autoSaveFilter.isNotEmpty() } { autoSaveFilter.isNotEmpty() }
@ -83,45 +82,22 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
messages.forEach { messages.forEach {
if (!canSaveMessage(it)) return@forEach if (!canSaveMessage(it)) return@forEach
asyncSaveExecutorService.submit { asyncSaveExecutorService.submit {
saveMessage(conversationId, it) saveMessage(conversationId.toString(), it)
} }
} }
} }
//called when a message is received context.event.subscribe(
Hooker.hook( ConversationUpdateEvent::class,
context.mappings.getMappedClass("callbacks", "FetchMessageCallback"),
"onFetchMessageComplete",
HookStage.BEFORE,
{ autoSaveFilter.isNotEmpty() } { autoSaveFilter.isNotEmpty() }
) { param -> ) { event ->
val message = Message(param.arg(0)) if (!canSaveInConversation(event.conversationId)) return@subscribe
val conversationId = message.messageDescriptor!!.conversationId!!
if (!canSaveInConversation(conversationId.toString())) return@hook
if (!canSaveMessage(message)) return@hook
asyncSaveExecutorService.submit { event.messages.forEach { message ->
saveMessage(conversationId, message) if (!canSaveMessage(message)) return@forEach
} asyncSaveExecutorService.submit {
} saveMessage(event.conversationId, message)
}
Hooker.hook(
context.mappings.getMappedClass("callbacks", "SendMessageCallback"),
"onSuccess",
HookStage.BEFORE,
{ autoSaveFilter.isNotEmpty() }
) {
val conversationUUID = messaging.openedConversationUUID ?: return@hook
runCatching {
messaging.conversationManager?.fetchConversationWithMessagesPaginated(
conversationUUID.toString(),
Long.MAX_VALUE,
10,
onSuccess = {},
onError = {}
)
}.onFailure {
CoreLogger.xposedLog("failed to save message", it)
} }
} }
} }

View File

@ -1,6 +1,7 @@
package me.rhunk.snapenhance.core.features.impl.messaging package me.rhunk.snapenhance.core.features.impl.messaging
import me.rhunk.snapenhance.common.ReceiversConfig import me.rhunk.snapenhance.common.ReceiversConfig
import me.rhunk.snapenhance.core.event.events.impl.ConversationUpdateEvent
import me.rhunk.snapenhance.core.event.events.impl.OnSnapInteractionEvent 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
@ -41,6 +42,21 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
finishAndRemoveTask() finishAndRemoveTask()
} }
} }
context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").apply {
hookConstructor(HookStage.AFTER) { param ->
conversationManagerDelegate = param.thisObject()
}
hook("onConversationUpdated", HookStage.BEFORE) { param ->
context.event.post(ConversationUpdateEvent(
conversationId = SnapUUID(param.arg(0)).toString(),
conversation = param.argNullable(1),
messages = param.arg<ArrayList<*>>(2).map { Message(it) },
).apply { adapter = param }) {
param.setArg(2, messages.map { it.instanceNonNull() }.toCollection(ArrayList()))
}
}
}
} }
fun getFeedCachedMessageIds(conversationId: String) = feedCachedSnapMessages[conversationId] fun getFeedCachedMessageIds(conversationId: String) = feedCachedSnapMessages[conversationId]
@ -83,11 +99,6 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
} }
} }
context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").apply {
hookConstructor(HookStage.AFTER) { param ->
conversationManagerDelegate = param.thisObject()
}
}
context.classCache.feedEntry.hookConstructor(HookStage.AFTER) { param -> context.classCache.feedEntry.hookConstructor(HookStage.AFTER) { param ->
val instance = param.thisObject<Any>() val instance = param.thisObject<Any>()

View File

@ -37,7 +37,6 @@ import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.util.media.PreviewUtils import me.rhunk.snapenhance.core.util.media.PreviewUtils
import me.rhunk.snapenhance.core.wrapper.impl.Message import me.rhunk.snapenhance.core.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.INIT_SYNC) { class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.INIT_SYNC) {
@ -245,7 +244,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
messages.reversed().forEach { message -> messages.reversed().forEach { message ->
if (!autoSave.canSaveMessage(message, headless = true)) return@forEach if (!autoSave.canSaveMessage(message, headless = true)) return@forEach
context.coroutineScope.launch(coroutineDispatcher) { context.coroutineScope.launch(coroutineDispatcher) {
autoSave.saveMessage(conversationId.toSnapUUID(), message) autoSave.saveMessage(conversationId, message)
} }
} }
}, },

View File

@ -2,11 +2,11 @@ package me.rhunk.snapenhance.core.features.impl.tweaks
import android.view.View import android.view.View
import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent
import me.rhunk.snapenhance.core.event.events.impl.ConversationUpdateEvent
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.util.hook.HookStage import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook import me.rhunk.snapenhance.core.util.hook.hook
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 PreventMessageListAutoScroll : Feature("PreventMessageListAutoScroll", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) { class PreventMessageListAutoScroll : Feature("PreventMessageListAutoScroll", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
@ -18,28 +18,28 @@ class PreventMessageListAutoScroll : Feature("PreventMessageListAutoScroll", loa
override fun onActivityCreate() { override fun onActivityCreate() {
if (!context.config.userInterface.preventMessageListAutoScroll.get()) return if (!context.config.userInterface.preventMessageListAutoScroll.get()) return
context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").hook("onConversationUpdated", HookStage.BEFORE) { param -> context.event.subscribe(ConversationUpdateEvent::class) { event ->
val updatedMessage = param.arg<ArrayList<*>>(2).map { Message(it) }.firstOrNull() ?: return@hook val updatedMessage = event.messages.firstOrNull() ?: return@subscribe
if (openedConversationId != updatedMessage.messageDescriptor?.conversationId.toString()) return@hook if (openedConversationId != updatedMessage.messageDescriptor?.conversationId.toString()) return@subscribe
// cancel if the message is already in focus // cancel if the message is already in focus
if (focusedMessages.entries.any { entry -> entry.value == updatedMessage.messageDescriptor?.messageId && entry.key.isAttachedToWindow }) return@hook if (focusedMessages.entries.any { entry -> entry.value == updatedMessage.messageDescriptor?.messageId && entry.key.isAttachedToWindow }) return@subscribe
val conversationLastMessages = context.database.getMessagesFromConversationId( val conversationLastMessages = context.database.getMessagesFromConversationId(
openedConversationId.toString(), openedConversationId.toString(),
4 4
) ?: return@hook ) ?: return@subscribe
if (conversationLastMessages.none { if (conversationLastMessages.none {
focusedMessages.entries.any { entry -> entry.value == it.clientMessageId.toLong() && entry.key.isAttachedToWindow } focusedMessages.entries.any { entry -> entry.value == it.clientMessageId.toLong() && entry.key.isAttachedToWindow }
}) { }) {
synchronized(delayedMessageUpdates) { synchronized(delayedMessageUpdates) {
if (firstFocusedMessageId == null) firstFocusedMessageId = conversationLastMessages.lastOrNull()?.clientMessageId?.toLong() if (firstFocusedMessageId == null) firstFocusedMessageId = conversationLastMessages.lastOrNull()?.clientMessageId?.toLong()
delayedMessageUpdates.add { delayedMessageUpdates.add {
param.invokeOriginal() event.adapter.invokeOriginal()
} }
} }
param.setResult(null) event.adapter.setResult(null)
} }
} }