commit 98d0f987145f022f92cd1f637c0dff403bf7e3ff
parent 202638841a23b5a2d25d11398c9dd934658ac291
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Fri, 1 Dec 2023 21:44:40 +0100
refactor: convert message locally
Diffstat:
7 files changed, 118 insertions(+), 37 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -581,9 +581,9 @@
}
}
},
- "snap_to_chat_media": {
- "name": "Snap to Chat Media",
- "description": "Converts snaps to chat external media"
+ "convert_message_locally": {
+ "name": "Convert Message Locally",
+ "description": "Converts snaps to chat external media locally. This appears in chat download context menu"
},
"app_passcode": {
"name": "App Passcode",
@@ -813,7 +813,8 @@
"chat_action_menu": {
"preview_button": "Preview",
"download_button": "Download",
- "delete_logged_message_button": "Delete Logged Message"
+ "delete_logged_message_button": "Delete Logged Message",
+ "convert_message": "Convert Message"
},
"opera_context_menu": {
diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt
@@ -10,7 +10,7 @@ class Experimental : ConfigContainer() {
val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory"; requireRestart() }
val spoof = container("spoof", Spoof()) { icon = "Fingerprint" }
- val snapToChatMedia = boolean("snap_to_chat_media") { requireRestart(); addNotices(FeatureNotice.UNSTABLE) }
+ val convertMessageLocally = boolean("convert_message_locally") { requireRestart() }
val appPasscode = string("app_passcode")
val appLockOnResume = boolean("app_lock_on_resume")
val infiniteStoryBoost = boolean("infinite_story_boost")
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/ConvertMessageLocally.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/ConvertMessageLocally.kt
@@ -0,0 +1,67 @@
+package me.rhunk.snapenhance.core.features.impl.experiments
+
+import me.rhunk.snapenhance.common.data.ContentType
+import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
+import me.rhunk.snapenhance.common.util.protobuf.ProtoWriter
+import me.rhunk.snapenhance.core.event.events.impl.BuildMessageEvent
+import me.rhunk.snapenhance.core.features.Feature
+import me.rhunk.snapenhance.core.features.FeatureLoadParams
+import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
+import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
+import me.rhunk.snapenhance.core.wrapper.impl.Message
+import me.rhunk.snapenhance.core.wrapper.impl.MessageContent
+
+class ConvertMessageLocally : Feature("Convert Message Edit", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
+ private val messageCache = mutableMapOf<Long, MessageContent>()
+
+ private fun dispatchMessageEdit(message: Message, restore: Boolean = false) {
+ val messageId = message.messageDescriptor!!.messageId!!
+ if (!restore) messageCache[messageId] = message.messageContent!!
+
+ context.runOnUiThread {
+ context.feature(Messaging::class).localUpdateMessage(
+ message.messageDescriptor!!.conversationId!!.toString(),
+ message
+ )
+ }
+ }
+
+ fun convertMessageInterface(messageInstance: Message) {
+ val actions = mutableMapOf<String, (Message) -> Unit>()
+ actions["restore_original"] = {
+ messageCache.remove(it.messageDescriptor!!.messageId!!)
+ dispatchMessageEdit(it, restore = true)
+ }
+
+ val contentType = messageInstance.messageContent?.contentType
+ if (contentType == ContentType.SNAP) {
+ actions["convert_external_media"] = convert@{ message ->
+ val snapMessageContent = ProtoReader(message.messageContent!!.content!!).followPath(11)
+ ?.getBuffer() ?: return@convert
+ message.messageContent!!.content = ProtoWriter().apply {
+ from(3) {
+ addBuffer(3, snapMessageContent)
+ }
+ }.toByteArray()
+ dispatchMessageEdit(message)
+ }
+ }
+
+ ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity).apply {
+ setItems(actions.keys.toTypedArray()) { _, which ->
+ actions.values.elementAt(which).invoke(messageInstance)
+ }
+ setPositiveButton(this@ConvertMessageLocally.context.translation["button.cancel"]) { dialog, _ ->
+ dialog.dismiss()
+ }
+ }.show()
+ }
+
+ override fun onActivityCreate() {
+ context.event.subscribe(BuildMessageEvent::class, priority = 2) {
+ val clientMessageId = it.message.messageDescriptor?.messageId ?: return@subscribe
+ if (!messageCache.containsKey(clientMessageId)) return@subscribe
+ it.message.messageContent = messageCache[clientMessageId]
+ }
+ }
+}+
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/SnapToChatMedia.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/SnapToChatMedia.kt
@@ -1,25 +0,0 @@
-package me.rhunk.snapenhance.core.features.impl.experiments
-
-import me.rhunk.snapenhance.common.data.ContentType
-import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
-import me.rhunk.snapenhance.common.util.protobuf.ProtoWriter
-import me.rhunk.snapenhance.core.event.events.impl.BuildMessageEvent
-import me.rhunk.snapenhance.core.features.Feature
-import me.rhunk.snapenhance.core.features.FeatureLoadParams
-
-class SnapToChatMedia : Feature("SnapToChatMedia", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
- override fun onActivityCreate() {
- if (!context.config.experimental.snapToChatMedia.get()) return
-
- context.event.subscribe(BuildMessageEvent::class, priority = 100) { event ->
- if (event.message.messageContent!!.contentType != ContentType.SNAP) return@subscribe
-
- val snapMessageContent = ProtoReader(event.message.messageContent!!.content!!).followPath(11)?.getBuffer() ?: return@subscribe
- event.message.messageContent!!.content = ProtoWriter().apply {
- from(3) {
- addBuffer(3, snapMessageContent)
- }
- }.toByteArray()
- }
- }
-}-
\ No newline at end of file
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Messaging.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Messaging.kt
@@ -61,6 +61,14 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
})
}
+ fun localUpdateMessage(conversationId: String, message: Message) {
+ conversationManagerDelegate?.let {
+ it::class.java.methods.first { method ->
+ method.name == "onConversationUpdated"
+ }.invoke(conversationManagerDelegate, conversationId.toSnapUUID().instanceNonNull(), null, mutableListOf(message.instanceNonNull()), mutableListOf<Any>())
+ }
+ }
+
override fun onActivityCreate() {
context.mappings.getMappedObjectNullable("FriendsFeedEventDispatcher").let { it as? Map<*, *> }?.let { mappings ->
findClass(mappings["class"].toString()).hook("onItemLongPress", HookStage.BEFORE) { param ->
@@ -75,8 +83,10 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
}
}
- context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").hookConstructor(HookStage.AFTER) { param ->
- conversationManagerDelegate = param.thisObject()
+ context.mappings.getMappedClass("callbacks", "ConversationManagerDelegate").apply {
+ hookConstructor(HookStage.AFTER) { param ->
+ conversationManagerDelegate = param.thisObject()
+ }
}
context.classCache.feedEntry.hookConstructor(HookStage.AFTER) { param ->
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
@@ -9,17 +9,17 @@ import me.rhunk.snapenhance.core.features.FeatureLoadParams
import me.rhunk.snapenhance.core.features.MessagingRuleFeature
import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride
import me.rhunk.snapenhance.core.features.impl.ScopeSync
+import me.rhunk.snapenhance.core.features.impl.Stories
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.features.impl.downloader.ProfilePictureDownloader
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.spying.HalfSwipeNotifier
+import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
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.BypassScreenshotDetection
-import me.rhunk.snapenhance.core.features.impl.Stories
+import me.rhunk.snapenhance.core.features.impl.tweaks.CameraTweaks
import me.rhunk.snapenhance.core.features.impl.ui.*
import me.rhunk.snapenhance.core.logger.CoreLogger
import me.rhunk.snapenhance.core.manager.Manager
@@ -70,6 +70,7 @@ class FeatureManager(
MenuViewInjector::class,
PreventReadReceipts::class,
MessageLogger::class,
+ ConvertMessageLocally::class,
SnapchatPlus::class,
DisableMetrics::class,
PreventMessageSending::class,
@@ -97,7 +98,6 @@ class FeatureManager(
AddFriendSourceSpoof::class,
DisableReplayInFF::class,
OldBitmojiSelfie::class,
- SnapToChatMedia::class,
FriendFeedMessagePreview::class,
HideStreakRestore::class,
HideFriendFeedEntry::class,
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt
@@ -13,6 +13,7 @@ import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
+import me.rhunk.snapenhance.core.features.impl.experiments.ConvertMessageLocally
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
import me.rhunk.snapenhance.core.ui.ViewTagState
import me.rhunk.snapenhance.core.ui.applyTheme
@@ -147,6 +148,33 @@ class ChatActionMenu : AbstractMenu() {
})
}
+ if (context.config.experimental.convertMessageLocally.get()) {
+ injectButton(Button(viewGroup.context).apply {
+ text = this@ChatActionMenu.context.translation["chat_action_menu.convert_message"]
+ setOnClickListener {
+ closeActionMenu()
+ messaging.conversationManager?.fetchMessage(
+ messaging.openedConversationUUID.toString(),
+ messaging.lastFocusedMessageId,
+ onSuccess = {
+ this@ChatActionMenu.context.runOnUiThread {
+ runCatching {
+ this@ChatActionMenu.context.feature(ConvertMessageLocally::class)
+ .convertMessageInterface(it)
+ }.onFailure {
+ this@ChatActionMenu.context.log.verbose("Failed to convert message: $it")
+ this@ChatActionMenu.context.shortToast("Failed to edit message: $it")
+ }
+ }
+ },
+ onError = {
+ this@ChatActionMenu.context.shortToast("Failed to fetch message: $it")
+ }
+ )
+ }
+ })
+ }
+
if (context.isDeveloper) {
viewGroup.addView(createContainer(viewGroup).apply {
val debugText = StringBuilder()