Skip to content

Commit

Permalink
feat(core/events): conversation updated event
Browse files Browse the repository at this point in the history
  • Loading branch information
rhunk committed Dec 12, 2023
1 parent 37daae3 commit d0ff3c3
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -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.MessageUpdate
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.MessagingRuleFeature
import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
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.Hooker
import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
Expand All @@ -19,20 +19,18 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
private val asyncSaveExecutorService = Executors.newSingleThreadExecutor()

private val messageLogger by lazy { context.feature(MessageLogger::class) }
private val messaging by lazy { context.feature(Messaging::class) }

private val autoSaveFilter by lazy {
context.config.messaging.autoSaveMessagesInConversations.get()
}

fun saveMessage(conversationId: SnapUUID, message: Message) {
fun saveMessage(conversationId: String, message: Message) {
val messageId = message.messageDescriptor!!.messageId!!
if (messageLogger.takeIf { it.isEnabled }?.isMessageDeleted(conversationId.toString(), messageId) == true) return
if (message.messageState != MessageState.COMMITTED) return
if (messageLogger.takeIf { it.isEnabled }?.isMessageDeleted(conversationId, messageId) == true) return

runCatching {
context.feature(Messaging::class).conversationManager?.updateMessage(
conversationId.toString(),
conversationId,
messageId,
MessageUpdate.SAVE
) {
Expand All @@ -49,6 +47,8 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
}

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 (message.messageMetadata!!.savedBy!!.any { uuid -> uuid.toString() == context.database.myUserId }) return false
val contentType = message.messageContent!!.contentType.toString()
Expand All @@ -69,9 +69,8 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
}

override fun asyncOnActivityCreate() {
//called when enter in a conversation (or when a message is sent)
Hooker.hook(
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"),
// called when enter in a conversation
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback").hook(
"onFetchConversationWithMessagesComplete",
HookStage.BEFORE,
{ autoSaveFilter.isNotEmpty() }
Expand All @@ -83,45 +82,22 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
messages.forEach {
if (!canSaveMessage(it)) return@forEach
asyncSaveExecutorService.submit {
saveMessage(conversationId, it)
saveMessage(conversationId.toString(), it)
}
}
}

//called when a message is received
Hooker.hook(
context.mappings.getMappedClass("callbacks", "FetchMessageCallback"),
"onFetchMessageComplete",
HookStage.BEFORE,
context.event.subscribe(
ConversationUpdateEvent::class,
{ autoSaveFilter.isNotEmpty() }
) { param ->
val message = Message(param.arg(0))
val conversationId = message.messageDescriptor!!.conversationId!!
if (!canSaveInConversation(conversationId.toString())) return@hook
if (!canSaveMessage(message)) return@hook
) { event ->
if (!canSaveInConversation(event.conversationId)) return@subscribe

asyncSaveExecutorService.submit {
saveMessage(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)
event.messages.forEach { message ->
if (!canSaveMessage(message)) return@forEach
asyncSaveExecutorService.submit {
saveMessage(event.conversationId, message)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package me.rhunk.snapenhance.core.features.impl.messaging

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.features.Feature
import me.rhunk.snapenhance.core.features.FeatureLoadParams
Expand Down Expand Up @@ -41,6 +42,21 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
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]
Expand Down Expand Up @@ -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 ->
val instance = param.thisObject<Any>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID
import kotlin.coroutines.suspendCoroutine

class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.INIT_SYNC) {
Expand Down Expand Up @@ -245,7 +244,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
messages.reversed().forEach { message ->
if (!autoSave.canSaveMessage(message, headless = true)) return@forEach
context.coroutineScope.launch(coroutineDispatcher) {
autoSave.saveMessage(conversationId.toSnapUUID(), message)
autoSave.saveMessage(conversationId, message)
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package me.rhunk.snapenhance.core.features.impl.tweaks

import android.view.View
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.FeatureLoadParams
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.wrapper.impl.Message
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID

class PreventMessageListAutoScroll : Feature("PreventMessageListAutoScroll", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
Expand All @@ -18,28 +18,28 @@ class PreventMessageListAutoScroll : Feature("PreventMessageListAutoScroll", loa
override fun onActivityCreate() {
if (!context.config.userInterface.preventMessageListAutoScroll.get()) return

context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").hook("onConversationUpdated", HookStage.BEFORE) { param ->
val updatedMessage = param.arg<ArrayList<*>>(2).map { Message(it) }.firstOrNull() ?: return@hook
if (openedConversationId != updatedMessage.messageDescriptor?.conversationId.toString()) return@hook
context.event.subscribe(ConversationUpdateEvent::class) { event ->
val updatedMessage = event.messages.firstOrNull() ?: return@subscribe
if (openedConversationId != updatedMessage.messageDescriptor?.conversationId.toString()) return@subscribe

// 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(
openedConversationId.toString(),
4
) ?: return@hook
) ?: return@subscribe

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) {
if (firstFocusedMessageId == null) firstFocusedMessageId = conversationLastMessages.lastOrNull()?.clientMessageId?.toLong()
delayedMessageUpdates.add {
param.invokeOriginal()
event.adapter.invokeOriginal()
}
}
param.setResult(null)
event.adapter.setResult(null)
}
}

Expand Down

0 comments on commit d0ff3c3

Please sign in to comment.