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:
Mcommon/src/main/assets/lang/en_US.json | 9+++++----
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Experimental.kt | 2+-
Acore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/ConvertMessageLocally.kt | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/SnapToChatMedia.kt | 26--------------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Messaging.kt | 14++++++++++++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/manager/impl/FeatureManager.kt | 8++++----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt | 28++++++++++++++++++++++++++++
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()