commit a63bca978238ffc6adec2997e1c3f7e30c9c4727
parent 17f81eb6827e1a966cd6dd19b9d0d6ced095b3e0
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Thu,  2 Nov 2023 14:51:19 +0100

feat: instant delete

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 4++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt | 1+
Acore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/InstantDelete.kt | 106+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/ConversationManager.kt | 4+---
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/MessageContent.kt | 2++
Acore/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/QuotedMessage.kt | 9+++++++++
Acore/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/QuotedMessageContent.kt | 11+++++++++++
8 files changed, 135 insertions(+), 4 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -344,6 +344,10 @@ "name": "Prevent Message Sending", "description": "Prevents sending certain types of messages" }, + "instant_delete": { + "name": "Instant Delete", + "description": "Removes the confirmation dialog when deleting messages" + }, "better_notifications": { "name": "Better Notifications", "description": "Adds more information in received notifications" 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 @@ -23,6 +23,7 @@ class MessagingTweaks : ConfigContainer() { customOptionTranslationPath = "features.options.notifications" nativeHooks() } + val instantDelete = boolean("instant_delete") { requireRestart() } val betterNotifications = multiple("better_notifications", "snap", "chat", "reply_button", "download_button", "mark_as_read_button", "group") { requireRestart() } val notificationBlacklist = multiple("notification_blacklist", *NotificationType.getIncomingValues().map { it.key }.toTypedArray()) { customOptionTranslationPath = "features.options.notifications" diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/InstantDelete.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/InstantDelete.kt @@ -0,0 +1,105 @@ +package me.rhunk.snapenhance.core.features.impl.messaging + +import android.view.View +import android.widget.TextView +import me.rhunk.snapenhance.common.data.MessageUpdate +import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent +import me.rhunk.snapenhance.core.features.Feature +import me.rhunk.snapenhance.core.features.FeatureLoadParams +import me.rhunk.snapenhance.core.ui.iterateParent +import me.rhunk.snapenhance.core.ui.triggerCloseTouchEvent +import me.rhunk.snapenhance.core.util.ktx.getId +import me.rhunk.snapenhance.core.util.ktx.getIdentifier +import me.rhunk.snapenhance.core.util.ktx.setObjectField +import me.rhunk.snapenhance.core.wrapper.impl.CallbackResult +import java.lang.reflect.Modifier + +class InstantDelete : Feature("InstantDelete", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) { + override fun asyncOnActivityCreate() { + if (!context.config.messaging.instantDelete.get()) return + val chatActionMenuOptions = listOf( + "chat_action_menu_erase_messages", + "chat_action_menu_erase_quote", + "chat_action_menu_erase_reply", + ).associateWith { context.resources.getString(context.resources.getIdentifier(it, "string")) } + + val chatActionMenuContainerID = context.resources.getId("chat_action_menu_container") + val actionMenuOptionId = context.resources.getId("action_menu_option") + val actionMenuOptionTextId = context.resources.getId("action_menu_option_text") + + context.event.subscribe(BindViewEvent::class) { event -> + if (event.view.id != actionMenuOptionId) return@subscribe + + val menuOptionText = event.view.findViewById<TextView>(actionMenuOptionTextId) ?: return@subscribe + if (!chatActionMenuOptions.values.contains(menuOptionText.text)) return@subscribe + + val viewModel = event.prevModel + + val nestedViewOnClickListenerField = viewModel::class.java.fields.find { + it.type == View.OnClickListener::class.java + } ?: return@subscribe + + val nestedViewOnClickListener = nestedViewOnClickListenerField.get(viewModel) as? View.OnClickListener ?: return@subscribe + + val chatViewModel = nestedViewOnClickListener::class.java.fields.find { + Modifier.isAbstract(it.type.modifiers) && runCatching { + it.get(nestedViewOnClickListener) + }.getOrNull().toString().startsWith("ChatViewModel") + }?.get(nestedViewOnClickListener) ?: return@subscribe + + //[convId]:arroyo-id:[messageId] + val (conversationId, messageId) = chatViewModel.toString().substringAfter("messageId=").substringBefore(",").split(":").let { + if (it.size != 3) return@let null + it[0] to it[2] + } ?: return@subscribe + + viewModel.setObjectField(nestedViewOnClickListenerField.name, View.OnClickListener { view -> + val onCallbackResult: CallbackResult = callbackResult@{ + if (it == null || it == "DUPLICATEREQUEST") return@callbackResult + context.log.error("Error deleting message $messageId: $it") + context.shortToast("Error deleting message $messageId: $it. Using fallback method") + context.runOnUiThread { + nestedViewOnClickListener.onClick(view) + } + } + + runCatching { + val conversationManager = context.feature(Messaging::class).conversationManager ?: return@runCatching + + if (chatActionMenuOptions["chat_action_menu_erase_quote"] == menuOptionText.text) { + conversationManager.fetchMessage(conversationId, messageId.toLong(), onSuccess = { message -> + val quotedMessage = message.messageContent.quotedMessage.takeIf { it.isPresent() }!! + + conversationManager.updateMessage( + conversationId, + quotedMessage.content.messageId, + MessageUpdate.ERASE, + onResult = onCallbackResult + ) + }, onError = { + onCallbackResult(it) + }) + return@runCatching + } + + conversationManager.updateMessage( + conversationId, + messageId.toLong(), + MessageUpdate.ERASE, + onResult = onCallbackResult + ) + }.onFailure { + context.log.error("Error deleting message $messageId", it) + onCallbackResult(it.message) + return@OnClickListener + } + + view.iterateParent { + if (it.id != chatActionMenuContainerID) return@iterateParent false + it.triggerCloseTouchEvent() + true + } + }) + } + } +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt @@ -15,7 +15,6 @@ import me.rhunk.snapenhance.core.features.impl.experiments.* import me.rhunk.snapenhance.core.features.impl.global.* import me.rhunk.snapenhance.core.features.impl.messaging.* import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger -import me.rhunk.snapenhance.core.features.impl.experiments.SnapToChatMedia import me.rhunk.snapenhance.core.features.impl.spying.StealthMode import me.rhunk.snapenhance.core.features.impl.tweaks.CameraTweaks import me.rhunk.snapenhance.core.features.impl.ui.* @@ -103,6 +102,7 @@ class FeatureManager( HideQuickAddFriendFeed::class, CallStartConfirmation::class, SnapPreview::class, + InstantDelete::class, ) initializeFeatures() 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 @@ -74,10 +74,9 @@ class ConversationManager(val context: ModContext, obj: Any) : AbstractWrapper(o conversationId.toSnapUUID().instanceNonNull(), messageId, CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback")) - .override("onSuccess") { param -> + .override("onFetchMessageComplete") { param -> onSuccess(Message(param.arg(0))) } - .override("onServerRequest", shouldUnhook = false) {} .override("onError") { onError(it.arg<Any>(0).toString()) }.build() @@ -96,7 +95,6 @@ class ConversationManager(val context: ModContext, obj: Any) : AbstractWrapper(o .override("onFetchMessageComplete") { param -> onSuccess(Message(param.arg(1))) } - .override("onServerRequest", shouldUnhook = false) {} .override("onError") { onError(it.arg<Any>(0).toString()) }.build() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/MessageContent.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/MessageContent.kt @@ -9,5 +9,7 @@ class MessageContent(obj: Any?) : AbstractWrapper(obj) { var content get() = instanceNonNull().getObjectField("mContent") as ByteArray set(value) = instanceNonNull().setObjectField("mContent", value) + val quotedMessage + get() = QuotedMessage(instanceNonNull().getObjectField("mQuotedMessage")) var contentType by enum("mContentType", ContentType.UNKNOWN) } \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/QuotedMessage.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/QuotedMessage.kt @@ -0,0 +1,8 @@ +package me.rhunk.snapenhance.core.wrapper.impl + +import me.rhunk.snapenhance.core.util.ktx.getObjectField +import me.rhunk.snapenhance.core.wrapper.AbstractWrapper + +class QuotedMessage(obj: Any?) : AbstractWrapper(obj) { + val content get() = QuotedMessageContent(instanceNonNull().getObjectField("mContent")) +}+ \ No newline at end of file diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/QuotedMessageContent.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/wrapper/impl/QuotedMessageContent.kt @@ -0,0 +1,10 @@ +package me.rhunk.snapenhance.core.wrapper.impl + +import me.rhunk.snapenhance.core.util.ktx.getObjectField +import me.rhunk.snapenhance.core.util.ktx.setObjectField +import me.rhunk.snapenhance.core.wrapper.AbstractWrapper + +class QuotedMessageContent(obj: Any?) : AbstractWrapper(obj) { + var messageId get() = instanceNonNull().getObjectField("mMessageId") as Long + set(value) = instanceNonNull().setObjectField("mMessageId", value) +}+ \ No newline at end of file