mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-12 13:17:42 +02:00
feat(manager): conversation preview (wip)
- add messaging bridge - refactor export chat messages
This commit is contained in:
@ -19,6 +19,7 @@ import me.rhunk.snapenhance.core.bridge.loadFromBridge
|
||||
import me.rhunk.snapenhance.core.data.SnapClassCache
|
||||
import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
|
||||
import me.rhunk.snapenhance.core.messaging.CoreMessagingBridge
|
||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||
import me.rhunk.snapenhance.core.util.hook.hook
|
||||
import kotlin.system.measureTimeMillis
|
||||
@ -116,6 +117,7 @@ class SnapEnhance {
|
||||
logCritical(null, throwable)
|
||||
}
|
||||
}
|
||||
bridgeClient.registerMessagingBridge(CoreMessagingBridge(this))
|
||||
|
||||
reloadConfig()
|
||||
actionManager.init()
|
||||
|
@ -25,16 +25,7 @@ import java.io.File
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
class ExportChatMessages : AbstractAction() {
|
||||
private val callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") }
|
||||
|
||||
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
||||
|
||||
private val enterConversationMethod by lazy {
|
||||
context.classCache.conversationManager.methods.first { it.name == "enterConversation" }
|
||||
}
|
||||
private val exitConversationMethod by lazy {
|
||||
context.classCache.conversationManager.methods.first { it.name == "exitConversation" }
|
||||
}
|
||||
private val fetchConversationWithMessagesPaginatedMethod by lazy {
|
||||
context.classCache.conversationManager.methods.first { it.name == "fetchConversationWithMessagesPaginated" }
|
||||
}
|
||||
@ -162,32 +153,6 @@ class ExportChatMessages : AbstractAction() {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun conversationAction(isEntering: Boolean, conversationId: String, conversationType: String?) = suspendCancellableCoroutine { continuation ->
|
||||
val callback = CallbackBuilder(callbackClass)
|
||||
.override("onSuccess") { _ ->
|
||||
continuation.resumeWith(Result.success(Unit))
|
||||
}
|
||||
.override("onError") {
|
||||
continuation.resumeWith(Result.failure(Exception("Failed to ${if (isEntering) "enter" else "exit"} conversation")))
|
||||
}.build()
|
||||
|
||||
if (isEntering) {
|
||||
enterConversationMethod.invoke(
|
||||
conversationManagerInstance,
|
||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||
enterConversationMethod.parameterTypes[1].enumConstants.first { it.toString() == conversationType },
|
||||
callback
|
||||
)
|
||||
} else {
|
||||
exitConversationMethod.invoke(
|
||||
conversationManagerInstance,
|
||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||
Long.MAX_VALUE,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchMessagesPaginated(conversationId: String, lastMessageId: Long, amount: Int) = suspendCancellableCoroutine { continuation ->
|
||||
val callback = CallbackBuilder(fetchConversationWithMessagesCallbackClass)
|
||||
.override("onFetchConversationWithMessagesComplete") { param ->
|
||||
@ -213,10 +178,6 @@ class ExportChatMessages : AbstractAction() {
|
||||
val conversationId = friendFeedEntry.key!!
|
||||
val conversationName = friendFeedEntry.feedDisplayName ?: friendFeedEntry.friendDisplayName!!.split("|").lastOrNull() ?: "unknown"
|
||||
|
||||
runCatching {
|
||||
conversationAction(true, conversationId, if (friendFeedEntry.feedDisplayName != null) "USERCREATEDGROUP" else "ONEONONE")
|
||||
}
|
||||
|
||||
logDialog(context.translation.format("chat_export.exporting_message", "conversation" to conversationName))
|
||||
|
||||
val foundMessages = fetchMessagesPaginated(conversationId, Long.MAX_VALUE, amount = 1).toMutableList()
|
||||
@ -266,10 +227,6 @@ class ExportChatMessages : AbstractAction() {
|
||||
logDialog("\n" + context.translation.format("chat_export.exported_to",
|
||||
"path" to outputFile.absolutePath.toString()
|
||||
) + "\n")
|
||||
|
||||
runCatching {
|
||||
conversationAction(false, conversationId, null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportChatForConversations(conversations: List<FriendFeedEntry>) {
|
||||
|
@ -16,6 +16,7 @@ import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||
import me.rhunk.snapenhance.bridge.e2ee.E2eeInterface
|
||||
import me.rhunk.snapenhance.bridge.scripting.IScripting
|
||||
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
|
||||
import me.rhunk.snapenhance.common.BuildConfig
|
||||
import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
|
||||
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
|
||||
@ -147,6 +148,8 @@ class BridgeClient(
|
||||
|
||||
fun getMessageLogger() = service.messageLogger
|
||||
|
||||
fun registerMessagingBridge(bridge: MessagingBridge) = service.registerMessagingBridge(bridge)
|
||||
|
||||
fun openSettingsOverlay() = service.openSettingsOverlay()
|
||||
fun closeSettingsOverlay() = service.closeSettingsOverlay()
|
||||
|
||||
|
@ -60,6 +60,10 @@ object MessageDecoder {
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun getEncodedMediaReferences(messageContent: MessageContent): List<String> {
|
||||
return getEncodedMediaReferences(gson.toJsonTree(messageContent.instanceNonNull()))
|
||||
}
|
||||
|
||||
fun getMediaReferences(messageContent: JsonElement): List<JsonElement> {
|
||||
return messageContent.asJsonObject.getAsJsonArray("mRemoteMediaReferences")
|
||||
.asSequence()
|
||||
|
@ -0,0 +1,145 @@
|
||||
package me.rhunk.snapenhance.core.messaging
|
||||
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
|
||||
import me.rhunk.snapenhance.bridge.snapclient.types.Message
|
||||
import me.rhunk.snapenhance.core.ModContext
|
||||
import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
|
||||
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
|
||||
import me.rhunk.snapenhance.core.util.CallbackBuilder
|
||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||
|
||||
|
||||
fun me.rhunk.snapenhance.core.wrapper.impl.Message.toBridge(): Message {
|
||||
return Message().also { output ->
|
||||
output.conversationId = this.messageDescriptor.conversationId.toString()
|
||||
output.senderId = this.senderId.toString()
|
||||
output.clientMessageId = this.messageDescriptor.messageId
|
||||
output.serverMessageId = this.orderKey
|
||||
output.contentType = this.messageContent.contentType?.id ?: -1
|
||||
output.content = this.messageContent.content
|
||||
output.mediaReferences = MessageDecoder.getEncodedMediaReferences(this.messageContent)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CoreMessagingBridge(
|
||||
private val context: ModContext
|
||||
) : MessagingBridge.Stub() {
|
||||
private val conversationManager get() = context.feature(Messaging::class).conversationManager
|
||||
|
||||
override fun fetchMessage(conversationId: String, clientMessageId: String): Message? {
|
||||
return runBlocking {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val callback = CallbackBuilder(
|
||||
context.mappings.getMappedClass("callbacks", "FetchMessageCallback")
|
||||
).override("onFetchMessageComplete") { param ->
|
||||
val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(1)).toBridge()
|
||||
continuation.resumeWith(Result.success(message))
|
||||
}
|
||||
.override("onServerRequest", shouldUnhook = false) {}
|
||||
.override("onError") {
|
||||
continuation.resumeWith(Result.success(null))
|
||||
}.build()
|
||||
|
||||
context.classCache.conversationManager.methods.first { it.name == "fetchMessage" }.invoke(
|
||||
conversationManager,
|
||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||
clientMessageId,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchMessageByServerId(
|
||||
conversationId: String,
|
||||
serverMessageId: String
|
||||
): Message? {
|
||||
return runBlocking {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val callback = CallbackBuilder(
|
||||
context.mappings.getMappedClass("callbacks", "FetchMessageCallback")
|
||||
).override("onFetchMessageComplete") { param ->
|
||||
val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(1)).toBridge()
|
||||
continuation.resumeWith(Result.success(message))
|
||||
}
|
||||
.override("onServerRequest", shouldUnhook = false) {}
|
||||
.override("onError") {
|
||||
continuation.resumeWith(Result.success(null))
|
||||
}.build()
|
||||
|
||||
val serverMessageIdentifier = context.androidContext.classLoader.loadClass("com.snapchat.client.messaging.ServerMessageIdentifier")
|
||||
.getConstructor(context.classCache.snapUUID, Long::class.javaPrimitiveType)
|
||||
.newInstance(SnapUUID.fromString(conversationId).instanceNonNull(), serverMessageId.toLong())
|
||||
|
||||
context.classCache.conversationManager.methods.first { it.name == "fetchMessageByServerId" }.invoke(
|
||||
conversationManager,
|
||||
serverMessageIdentifier,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchConversationWithMessagesPaginated(
|
||||
conversationId: String,
|
||||
limit: Int,
|
||||
beforeMessageId: Long
|
||||
): List<Message>? {
|
||||
return runBlocking {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val callback = CallbackBuilder(
|
||||
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback")
|
||||
).override("onFetchConversationWithMessagesComplete") { param ->
|
||||
val messagesList = param.arg<List<*>>(1).map {
|
||||
me.rhunk.snapenhance.core.wrapper.impl.Message(it).toBridge()
|
||||
}
|
||||
continuation.resumeWith(Result.success(messagesList))
|
||||
}
|
||||
.override("onServerRequest", shouldUnhook = false) {}
|
||||
.override("onError") {
|
||||
continuation.resumeWith(Result.success(null))
|
||||
}.build()
|
||||
|
||||
context.classCache.conversationManager.methods.first { it.name == "fetchConversationWithMessagesPaginated" }.invoke(
|
||||
conversationManager,
|
||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||
beforeMessageId,
|
||||
limit,
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateMessage(
|
||||
conversationId: String,
|
||||
clientMessageId: String,
|
||||
messageUpdate: String
|
||||
): String? {
|
||||
return runBlocking {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
val callback = CallbackBuilder(
|
||||
context.mappings.getMappedClass("callbacks", "Callback")
|
||||
).override("onSuccess") {
|
||||
continuation.resumeWith(Result.success(null))
|
||||
}
|
||||
.override("onError") {
|
||||
continuation.resumeWith(Result.success(it.arg<Any>(0).toString()))
|
||||
}.build()
|
||||
|
||||
context.classCache.conversationManager.methods.first { it.name == "updateMessage" }.invoke(
|
||||
conversationManager,
|
||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||
clientMessageId,
|
||||
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == messageUpdate },
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOneToOneConversationId(userId: String) = context.database.getConversationLinkFromUserId(userId)?.clientConversationId
|
||||
}
|
Reference in New Issue
Block a user