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:
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