feat: instant delete

This commit is contained in:
rhunk 2023-11-02 14:51:19 +01:00
parent 17f81eb682
commit a63bca9782
8 changed files with 132 additions and 4 deletions

View File

@ -344,6 +344,10 @@
"name": "Prevent Message Sending", "name": "Prevent Message Sending",
"description": "Prevents sending certain types of messages" "description": "Prevents sending certain types of messages"
}, },
"instant_delete": {
"name": "Instant Delete",
"description": "Removes the confirmation dialog when deleting messages"
},
"better_notifications": { "better_notifications": {
"name": "Better Notifications", "name": "Better Notifications",
"description": "Adds more information in received notifications" "description": "Adds more information in received notifications"

View File

@ -23,6 +23,7 @@ class MessagingTweaks : ConfigContainer() {
customOptionTranslationPath = "features.options.notifications" customOptionTranslationPath = "features.options.notifications"
nativeHooks() 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 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()) { val notificationBlacklist = multiple("notification_blacklist", *NotificationType.getIncomingValues().map { it.key }.toTypedArray()) {
customOptionTranslationPath = "features.options.notifications" customOptionTranslationPath = "features.options.notifications"

View File

@ -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
}
})
}
}
}

View File

@ -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.global.*
import me.rhunk.snapenhance.core.features.impl.messaging.* import me.rhunk.snapenhance.core.features.impl.messaging.*
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.experiments.SnapToChatMedia
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode 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.tweaks.CameraTweaks
import me.rhunk.snapenhance.core.features.impl.ui.* import me.rhunk.snapenhance.core.features.impl.ui.*
@ -103,6 +102,7 @@ class FeatureManager(
HideQuickAddFriendFeed::class, HideQuickAddFriendFeed::class,
CallStartConfirmation::class, CallStartConfirmation::class,
SnapPreview::class, SnapPreview::class,
InstantDelete::class,
) )
initializeFeatures() initializeFeatures()

View File

@ -74,10 +74,9 @@ class ConversationManager(val context: ModContext, obj: Any) : AbstractWrapper(o
conversationId.toSnapUUID().instanceNonNull(), conversationId.toSnapUUID().instanceNonNull(),
messageId, messageId,
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback")) CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
.override("onSuccess") { param -> .override("onFetchMessageComplete") { param ->
onSuccess(Message(param.arg(0))) onSuccess(Message(param.arg(0)))
} }
.override("onServerRequest", shouldUnhook = false) {}
.override("onError") { .override("onError") {
onError(it.arg<Any>(0).toString()) onError(it.arg<Any>(0).toString())
}.build() }.build()
@ -96,7 +95,6 @@ class ConversationManager(val context: ModContext, obj: Any) : AbstractWrapper(o
.override("onFetchMessageComplete") { param -> .override("onFetchMessageComplete") { param ->
onSuccess(Message(param.arg(1))) onSuccess(Message(param.arg(1)))
} }
.override("onServerRequest", shouldUnhook = false) {}
.override("onError") { .override("onError") {
onError(it.arg<Any>(0).toString()) onError(it.arg<Any>(0).toString())
}.build() }.build()

View File

@ -9,5 +9,7 @@ class MessageContent(obj: Any?) : AbstractWrapper(obj) {
var content var content
get() = instanceNonNull().getObjectField("mContent") as ByteArray get() = instanceNonNull().getObjectField("mContent") as ByteArray
set(value) = instanceNonNull().setObjectField("mContent", value) set(value) = instanceNonNull().setObjectField("mContent", value)
val quotedMessage
get() = QuotedMessage(instanceNonNull().getObjectField("mQuotedMessage"))
var contentType by enum("mContentType", ContentType.UNKNOWN) var contentType by enum("mContentType", ContentType.UNKNOWN)
} }

View File

@ -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"))
}

View File

@ -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)
}