commit 008c094cd4e1dfa31991a3102a01c73329ec3cdf
parent 0c99ae15ba87871403a7c5345707ea6d76a5c194
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed, 26 Feb 2025 16:22:14 +0100

feat(core/tweaks): double tap chat action
- like, delete, copy message text, mark as read

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 10++++++++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt | 1+
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt | 1+
Acore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/DoubleTapChatAction.kt | 57+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/ConversationManager.kt | 21+++++++++++++++++++++
Mmapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt | 1+
Amapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ChatEventDispatcherMapper.kt | 19+++++++++++++++++++
7 files changed, 110 insertions(+), 0 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -743,6 +743,10 @@ "remove_groups_locked_status": { "name": "Remove Groups Locked Status", "description": "Allows you to view group information after being kicked" + }, + "double_tap_chat_action": { + "name": "Double Tap Chat Action", + "description": "Performs a custom action when double tapping a message in chat" } } }, @@ -1385,6 +1389,12 @@ "not_subscribed": "Not Subscribed", "basic": "Basic", "ad_free": "Ad Free" + }, + "double_tap_chat_action": { + "like_message": "Like Message", + "copy_text": "Copy Text to Clipboard", + "delete_message": "Delete Message", + "mark_as_read": "Mark as Read" } } }, diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt @@ -99,4 +99,5 @@ class MessagingTweaks : ConfigContainer() { val bypassMessageRetentionPolicy = boolean("bypass_message_retention_policy") { addNotices(FeatureNotice.UNSTABLE); requireRestart() } val bypassMessageActionRestrictions = boolean("bypass_message_action_restrictions") { requireRestart() } val removeGroupsLockedStatus = boolean("remove_groups_locked_status") { requireRestart() } + val doubleTapChatAction = unique("double_tap_chat_action", "like_message", "copy_text", "delete_message", "mark_as_read") { requireRestart() } } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt @@ -141,6 +141,7 @@ class FeatureManager( BetterTranscript(), VoiceNoteOverride(), FriendNotes(), + DoubleTapChatAction(), ) features.values.toList().forEach { feature -> diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/DoubleTapChatAction.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/tweaks/DoubleTapChatAction.kt @@ -0,0 +1,56 @@ +package me.rhunk.snapenhance.core.features.impl.tweaks + +import me.rhunk.snapenhance.common.data.ContentType +import me.rhunk.snapenhance.common.data.MessageUpdate +import me.rhunk.snapenhance.common.util.ktx.copyToClipboard +import me.rhunk.snapenhance.common.util.ktx.findFieldsToString +import me.rhunk.snapenhance.common.util.protobuf.ProtoReader +import me.rhunk.snapenhance.core.features.Feature +import me.rhunk.snapenhance.core.features.impl.messaging.Messaging +import me.rhunk.snapenhance.core.util.hook.HookStage +import me.rhunk.snapenhance.core.util.hook.hook +import me.rhunk.snapenhance.core.wrapper.impl.getMessageText +import me.rhunk.snapenhance.mapper.impl.ChatEventDispatcherMapper + +class DoubleTapChatAction: Feature("Double Tap Chat Action") { + override fun init() { + var action = context.config.messaging.doubleTapChatAction.getNullable() ?: return + + context.mappings.useMapper(ChatEventDispatcherMapper::class) { + classReference.getAsClass()?.hook("onChatItemDoubleClickEvent", HookStage.BEFORE) { param -> + param.setResult(null) + val event = param.arg<Any>(0) + val viewModel = event.javaClass.findFieldsToString(event, once = true) { field, value -> value.contains("ChatViewModel") }.firstOrNull()?.get(event)?.toString() ?: return@hook + + val (conversationId, _, clientMessageId) = viewModel.substringAfter("messageId=").substringBefore(",").split(":").takeIf { it.size == 3 } ?: return@hook + + val messageId = clientMessageId.toLongOrNull() ?: return@hook + + if (action == "like_message") { + context.feature(Messaging::class).conversationManager?.reactToMessage( + conversationId, + messageId, + intentionType = 1L, + onError = {}, + onSuccess = {} + ) + } + + if (action == "copy_text") { + var messageContent = context.database.getConversationMessageFromId(messageId)?.messageContent ?: return@hook + var proto = ProtoReader(messageContent).followPath(4, 4) ?: return@hook + context.androidContext.copyToClipboard(proto.getBuffer().getMessageText(ContentType.fromMessageContainer(proto) ?: ContentType.CHAT) ?: return@hook, "Chat Message") + } + + if (action == "delete_message" || action == "mark_as_read") { + context.feature(Messaging::class).conversationManager?.updateMessage( + conversationId, + messageId, + if (action == "delete_message") MessageUpdate.ERASE else MessageUpdate.READ, + onResult = {} + ) + } + } + } + } +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/ConversationManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/ConversationManager.kt @@ -27,6 +27,7 @@ class ConversationManager( private val clearConversation by lazy { findMethodByName("clearConversation") } private val getOneOnOneConversationIds by lazy { findMethodByName("getOneOnOneConversationIds") } private val dismissStreakRestore by lazy { findMethodByName("dismissStreakRestore") } + private val reactToMessageMethod by lazy { findMethodByName("reactToMessage") } private fun getCallbackClass(name: String): Class<*> { @@ -183,4 +184,24 @@ class ConversationManager( .override("onError") { onError(it.arg<Any>(0).toString()) }.build() dismissStreakRestore.invoke(instanceNonNull(), conversationId.toSnapUUID().instanceNonNull(), callback) } + + fun reactToMessage(conversationId: String, messageId: Long, emoji: String? = null, intentionType: Long? = null, onSuccess: () -> Unit, onError: (error: String) -> Unit) { + reactToMessageMethod.invoke( + instanceNonNull(), + conversationId.toSnapUUID().instanceNonNull(), + messageId, + reactToMessageMethod.parameterTypes[2].dataBuilder { + set("mEmoji", emoji) + set("mIntentionType", intentionType) + }, + reactToMessageMethod.parameterTypes[3].dataBuilder { + set("mMetricsMessageMediaType", "NO_MEDIA") + set("mMetricsMessageType", "TEXT") + set("mReactionSource", "NONE") + }, + CallbackBuilder(getCallbackClass("Callback")) + .override("onSuccess") { onSuccess() } + .override("onError") { onError(it.arg<Any>(0).toString()) }.build() + ) + } } \ No newline at end of file diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/ClassMapper.kt @@ -29,6 +29,7 @@ class ClassMapper( PlusSubscriptionMapper(), StoryBoostStateMapper(), FriendsFeedEventDispatcherMapper(), + ChatEventDispatcherMapper(), CompositeConfigurationProviderMapper(), ScoreUpdateMapper(), FriendRelationshipChangerMapper(), diff --git a/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ChatEventDispatcherMapper.kt b/mapper/src/main/kotlin/me/rhunk/snapenhance/mapper/impl/ChatEventDispatcherMapper.kt @@ -0,0 +1,18 @@ +package me.rhunk.snapenhance.mapper.impl + +import me.rhunk.snapenhance.mapper.AbstractClassMapper +import me.rhunk.snapenhance.mapper.ext.getClassName + +class ChatEventDispatcherMapper : AbstractClassMapper("ChatEventDispatcher") { + val classReference = classReference("class") + + init { + mapper { + for (clazz in classes) { + if (clazz.methods.firstOrNull { it.name == "onChatItemDoubleClickEvent" } == null) continue + classReference.set(clazz.getClassName()) + return@mapper + } + } + } +}+ \ No newline at end of file