mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-05-22 19:08:58 +02:00
refactor: conversation manager wrapper
- fix auto save in background
This commit is contained in:
parent
8d1c9a87ad
commit
17f81eb682
@ -105,6 +105,11 @@ enum class MediaReferenceType {
|
|||||||
UNASSIGNED, OVERLAY, IMAGE, VIDEO, ASSET_BUNDLE, AUDIO, ANIMATED_IMAGE, FONT, WEB_VIEW_CONTENT, VIDEO_NO_AUDIO
|
UNASSIGNED, OVERLAY, IMAGE, VIDEO, ASSET_BUNDLE, AUDIO, ANIMATED_IMAGE, FONT, WEB_VIEW_CONTENT, VIDEO_NO_AUDIO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class MessageUpdate {
|
||||||
|
UNKNOWN, READ, RELEASE, SAVE, UNSAVE, ERASE, SCREENSHOT, SCREEN_RECORD, REPLAY, REACTION, REMOVEREACTION, REVOKETRANSCRIPTION, ALLOWTRANSCRIPTION, ERASESAVEDSTORYMEDIA
|
||||||
|
}
|
||||||
|
|
||||||
enum class FriendLinkType(val value: Int, val shortName: String) {
|
enum class FriendLinkType(val value: Int, val shortName: String) {
|
||||||
MUTUAL(0, "mutual"),
|
MUTUAL(0, "mutual"),
|
||||||
OUTGOING(1, "outgoing"),
|
OUTGOING(1, "outgoing"),
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.common.util.ktx
|
|
||||||
|
|
||||||
fun String.longHashCode(): Long {
|
|
||||||
var h = 1125899906842597L
|
|
||||||
for (element in this) h = 31 * h + element.code.toLong()
|
|
||||||
return h
|
|
||||||
}
|
|
@ -0,0 +1,36 @@
|
|||||||
|
package me.rhunk.snapenhance.common.util.ktx
|
||||||
|
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
|
||||||
|
fun String.longHashCode(): Long {
|
||||||
|
var h = 1125899906842597L
|
||||||
|
for (element in this) h = 31 * h + element.code.toLong()
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun Class<*>.findFields(once: Boolean, crossinline predicate: (field: Field) -> Boolean): List<Field>{
|
||||||
|
var clazz: Class<*>? = this
|
||||||
|
val fields = mutableListOf<Field>()
|
||||||
|
|
||||||
|
while (clazz != null) {
|
||||||
|
if (once) {
|
||||||
|
clazz.declaredFields.firstOrNull(predicate)?.let { return listOf(it) }
|
||||||
|
} else {
|
||||||
|
fields.addAll(clazz.declaredFields.filter(predicate))
|
||||||
|
}
|
||||||
|
clazz = clazz.superclass ?: break
|
||||||
|
}
|
||||||
|
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun Class<*>.findFieldsToString(instance: Any? = null, once: Boolean = false, crossinline predicate: (field: Field, value: String) -> Boolean): List<Field> {
|
||||||
|
return this.findFields(once = once) {
|
||||||
|
try {
|
||||||
|
it.isAccessible = true
|
||||||
|
return@findFields it.get(instance)?.let { it1 -> predicate(it, it1.toString()) } == true
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
return@findFields false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -67,6 +67,8 @@ class ModContext(
|
|||||||
|
|
||||||
val isDeveloper by lazy { config.scripting.developerMode.get() }
|
val isDeveloper by lazy { config.scripting.developerMode.get() }
|
||||||
|
|
||||||
|
var isMainActivityPaused = false
|
||||||
|
|
||||||
fun <T : Feature> feature(featureClass: KClass<T>): T {
|
fun <T : Feature> feature(featureClass: KClass<T>): T {
|
||||||
return features.get(featureClass)!!
|
return features.get(featureClass)!!
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,6 @@ class SnapEnhance {
|
|||||||
}
|
}
|
||||||
private lateinit var appContext: ModContext
|
private lateinit var appContext: ModContext
|
||||||
private var isBridgeInitialized = false
|
private var isBridgeInitialized = false
|
||||||
private var isActivityPaused = false
|
|
||||||
|
|
||||||
private fun hookMainActivity(methodName: String, stage: HookStage = HookStage.AFTER, block: Activity.() -> Unit) {
|
private fun hookMainActivity(methodName: String, stage: HookStage = HookStage.AFTER, block: Activity.() -> Unit) {
|
||||||
Activity::class.java.hook(methodName, stage, { isBridgeInitialized }) { param ->
|
Activity::class.java.hook(methodName, stage, { isBridgeInitialized }) { param ->
|
||||||
@ -91,14 +90,14 @@ class SnapEnhance {
|
|||||||
|
|
||||||
hookMainActivity("onPause") {
|
hookMainActivity("onPause") {
|
||||||
appContext.bridgeClient.closeSettingsOverlay()
|
appContext.bridgeClient.closeSettingsOverlay()
|
||||||
isActivityPaused = true
|
appContext.isMainActivityPaused = true
|
||||||
}
|
}
|
||||||
|
|
||||||
var activityWasResumed = false
|
var activityWasResumed = false
|
||||||
//we need to reload the config when the app is resumed
|
//we need to reload the config when the app is resumed
|
||||||
//FIXME: called twice at first launch
|
//FIXME: called twice at first launch
|
||||||
hookMainActivity("onResume") {
|
hookMainActivity("onResume") {
|
||||||
isActivityPaused = false
|
appContext.isMainActivityPaused = false
|
||||||
if (!activityWasResumed) {
|
if (!activityWasResumed) {
|
||||||
activityWasResumed = true
|
activityWasResumed = true
|
||||||
return@hookMainActivity
|
return@hookMainActivity
|
||||||
@ -175,7 +174,7 @@ class SnapEnhance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun runLater(task: () -> Unit) {
|
fun runLater(task: () -> Unit) {
|
||||||
if (isActivityPaused) {
|
if (appContext.isMainActivityPaused) {
|
||||||
tasks.add(task)
|
tasks.add(task)
|
||||||
} else {
|
} else {
|
||||||
task()
|
task()
|
||||||
|
@ -18,18 +18,11 @@ import me.rhunk.snapenhance.core.logger.CoreLogger
|
|||||||
import me.rhunk.snapenhance.core.messaging.ExportFormat
|
import me.rhunk.snapenhance.core.messaging.ExportFormat
|
||||||
import me.rhunk.snapenhance.core.messaging.MessageExporter
|
import me.rhunk.snapenhance.core.messaging.MessageExporter
|
||||||
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper
|
||||||
import me.rhunk.snapenhance.core.util.CallbackBuilder
|
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.Message
|
import me.rhunk.snapenhance.core.wrapper.impl.Message
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
class ExportChatMessages : AbstractAction() {
|
class ExportChatMessages : AbstractAction() {
|
||||||
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
|
||||||
private val fetchConversationWithMessagesPaginatedMethod by lazy {
|
|
||||||
context.classCache.conversationManager.methods.first { it.name == "fetchConversationWithMessagesPaginated" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val dialogLogs = mutableListOf<String>()
|
private val dialogLogs = mutableListOf<String>()
|
||||||
private var currentActionDialog: AlertDialog? = null
|
private var currentActionDialog: AlertDialog? = null
|
||||||
|
|
||||||
@ -149,24 +142,14 @@ class ExportChatMessages : AbstractAction() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchMessagesPaginated(conversationId: String, lastMessageId: Long, amount: Int) = suspendCancellableCoroutine { continuation ->
|
private suspend fun fetchMessagesPaginated(conversationId: String, lastMessageId: Long, amount: Int): List<Message> = suspendCancellableCoroutine { continuation ->
|
||||||
val callback = CallbackBuilder(fetchConversationWithMessagesCallbackClass)
|
context.feature(Messaging::class).conversationManager?.fetchConversationWithMessagesPaginated(conversationId,
|
||||||
.override("onFetchConversationWithMessagesComplete") { param ->
|
|
||||||
val messagesList = param.arg<List<*>>(1).map { Message(it) }
|
|
||||||
continuation.resumeWith(Result.success(messagesList))
|
|
||||||
}
|
|
||||||
.override("onServerRequest", shouldUnhook = false) {}
|
|
||||||
.override("onError") {
|
|
||||||
continuation.resumeWith(Result.failure(Exception("Failed to fetch messages")))
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
fetchConversationWithMessagesPaginatedMethod.invoke(
|
|
||||||
context.feature(Messaging::class).conversationManager,
|
|
||||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
|
||||||
lastMessageId,
|
lastMessageId,
|
||||||
amount,
|
amount, onSuccess = { messages ->
|
||||||
callback
|
continuation.resumeWith(Result.success(messages))
|
||||||
)
|
}, onError = {
|
||||||
|
continuation.resumeWith(Result.success(emptyList()))
|
||||||
|
}) ?: continuation.resumeWith(Result.success(emptyList()))
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun exportFullConversation(friendFeedEntry: FriendFeedEntry) {
|
private suspend fun exportFullConversation(friendFeedEntry: FriendFeedEntry) {
|
||||||
|
@ -9,6 +9,7 @@ class SnapClassCache (
|
|||||||
val presenceSession by lazy { findClass("com.snapchat.talkcorev3.PresenceSession\$CppProxy") }
|
val presenceSession by lazy { findClass("com.snapchat.talkcorev3.PresenceSession\$CppProxy") }
|
||||||
val message by lazy { findClass("com.snapchat.client.messaging.Message") }
|
val message by lazy { findClass("com.snapchat.client.messaging.Message") }
|
||||||
val messageUpdateEnum by lazy { findClass("com.snapchat.client.messaging.MessageUpdate") }
|
val messageUpdateEnum by lazy { findClass("com.snapchat.client.messaging.MessageUpdate") }
|
||||||
|
val serverMessageIdentifier by lazy { findClass("com.snapchat.client.messaging.ServerMessageIdentifier") }
|
||||||
val unifiedGrpcService by lazy { findClass("com.snapchat.client.grpc.UnifiedGrpcService\$CppProxy") }
|
val unifiedGrpcService by lazy { findClass("com.snapchat.client.grpc.UnifiedGrpcService\$CppProxy") }
|
||||||
val networkApi by lazy { findClass("com.snapchat.client.network_api.NetworkApi\$CppProxy") }
|
val networkApi by lazy { findClass("com.snapchat.client.network_api.NetworkApi\$CppProxy") }
|
||||||
val messageDestinations by lazy { findClass("com.snapchat.client.messaging.MessageDestinations") }
|
val messageDestinations by lazy { findClass("com.snapchat.client.messaging.MessageDestinations") }
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
package me.rhunk.snapenhance.core.features.impl.messaging
|
package me.rhunk.snapenhance.core.features.impl.messaging
|
||||||
|
|
||||||
import me.rhunk.snapenhance.common.data.MessageState
|
import me.rhunk.snapenhance.common.data.MessageState
|
||||||
|
import me.rhunk.snapenhance.common.data.MessageUpdate
|
||||||
import me.rhunk.snapenhance.common.data.MessagingRuleType
|
import me.rhunk.snapenhance.common.data.MessagingRuleType
|
||||||
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
import me.rhunk.snapenhance.core.features.FeatureLoadParams
|
||||||
import me.rhunk.snapenhance.core.features.MessagingRuleFeature
|
import me.rhunk.snapenhance.core.features.MessagingRuleFeature
|
||||||
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.spying.StealthMode
|
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
|
||||||
import me.rhunk.snapenhance.core.logger.CoreLogger
|
import me.rhunk.snapenhance.core.logger.CoreLogger
|
||||||
import me.rhunk.snapenhance.core.util.CallbackBuilder
|
|
||||||
import me.rhunk.snapenhance.core.util.hook.HookStage
|
import me.rhunk.snapenhance.core.util.hook.HookStage
|
||||||
import me.rhunk.snapenhance.core.util.hook.Hooker
|
import me.rhunk.snapenhance.core.util.hook.Hooker
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
@ -21,14 +21,6 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
|||||||
private val messageLogger by lazy { context.feature(MessageLogger::class) }
|
private val messageLogger by lazy { context.feature(MessageLogger::class) }
|
||||||
private val messaging by lazy { context.feature(Messaging::class) }
|
private val messaging by lazy { context.feature(Messaging::class) }
|
||||||
|
|
||||||
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
|
|
||||||
private val callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") }
|
|
||||||
|
|
||||||
private val updateMessageMethod by lazy { context.classCache.conversationManager.methods.first { it.name == "updateMessage" } }
|
|
||||||
private val fetchConversationWithMessagesPaginatedMethod by lazy {
|
|
||||||
context.classCache.conversationManager.methods.first { it.name == "fetchConversationWithMessagesPaginated" }
|
|
||||||
}
|
|
||||||
|
|
||||||
private val autoSaveFilter by lazy {
|
private val autoSaveFilter by lazy {
|
||||||
context.config.messaging.autoSaveMessagesInConversations.get()
|
context.config.messaging.autoSaveMessagesInConversations.get()
|
||||||
}
|
}
|
||||||
@ -39,20 +31,17 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
|||||||
if (message.messageState != MessageState.COMMITTED) return
|
if (message.messageState != MessageState.COMMITTED) return
|
||||||
|
|
||||||
runCatching {
|
runCatching {
|
||||||
val callback = CallbackBuilder(callbackClass)
|
context.feature(Messaging::class).conversationManager?.updateMessage(
|
||||||
.override("onError") {
|
conversationId.toString(),
|
||||||
context.log.warn("Error saving message $messageId")
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
updateMessageMethod.invoke(
|
|
||||||
context.feature(Messaging::class).conversationManager,
|
|
||||||
conversationId.instanceNonNull(),
|
|
||||||
messageId,
|
messageId,
|
||||||
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == "SAVE" },
|
MessageUpdate.SAVE
|
||||||
callback
|
) {
|
||||||
)
|
if (it != null) {
|
||||||
|
context.log.warn("Error saving message $messageId: $it")
|
||||||
|
}
|
||||||
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
CoreLogger.xposedLog("Error saving message $messageId", it)
|
context.log.error("Error saving message $messageId", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
//delay between saves
|
//delay between saves
|
||||||
@ -60,6 +49,7 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun canSaveMessage(message: Message): Boolean {
|
private fun canSaveMessage(message: Message): Boolean {
|
||||||
|
if (context.mainActivity == null || context.isMainActivityPaused) return false
|
||||||
if (message.messageMetadata.savedBy.any { uuid -> uuid.toString() == context.database.myUserId }) return false
|
if (message.messageMetadata.savedBy.any { uuid -> uuid.toString() == context.database.myUserId }) return false
|
||||||
val contentType = message.messageContent.contentType.toString()
|
val contentType = message.messageContent.contentType.toString()
|
||||||
|
|
||||||
@ -121,14 +111,14 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
|
|||||||
HookStage.BEFORE,
|
HookStage.BEFORE,
|
||||||
{ autoSaveFilter.isNotEmpty() }
|
{ autoSaveFilter.isNotEmpty() }
|
||||||
) {
|
) {
|
||||||
val callback = CallbackBuilder(fetchConversationWithMessagesCallbackClass).build()
|
|
||||||
val conversationUUID = messaging.openedConversationUUID ?: return@hook
|
val conversationUUID = messaging.openedConversationUUID ?: return@hook
|
||||||
runCatching {
|
runCatching {
|
||||||
fetchConversationWithMessagesPaginatedMethod.invoke(
|
messaging.conversationManager?.fetchConversationWithMessagesPaginated(
|
||||||
messaging.conversationManager, conversationUUID.instanceNonNull(),
|
conversationUUID.toString(),
|
||||||
Long.MAX_VALUE,
|
Long.MAX_VALUE,
|
||||||
10,
|
10,
|
||||||
callback
|
onSuccess = {},
|
||||||
|
onError = {}
|
||||||
)
|
)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
CoreLogger.xposedLog("failed to save message", it)
|
CoreLogger.xposedLog("failed to save message", it)
|
||||||
|
@ -9,12 +9,13 @@ import me.rhunk.snapenhance.core.util.hook.HookStage
|
|||||||
import me.rhunk.snapenhance.core.util.hook.Hooker
|
import me.rhunk.snapenhance.core.util.hook.Hooker
|
||||||
import me.rhunk.snapenhance.core.util.hook.hook
|
import me.rhunk.snapenhance.core.util.hook.hook
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
|
import me.rhunk.snapenhance.core.wrapper.impl.ConversationManager
|
||||||
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
|
||||||
|
|
||||||
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
||||||
private var _conversationManager: Any? = null
|
var conversationManager: ConversationManager? = null
|
||||||
val conversationManager: Any?
|
private set
|
||||||
get() = _conversationManager
|
|
||||||
|
|
||||||
var openedConversationUUID: SnapUUID? = null
|
var openedConversationUUID: SnapUUID? = null
|
||||||
private set
|
private set
|
||||||
@ -28,7 +29,7 @@ class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_C
|
|||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
Hooker.hookConstructor(context.classCache.conversationManager, HookStage.BEFORE) { param ->
|
Hooker.hookConstructor(context.classCache.conversationManager, HookStage.BEFORE) { param ->
|
||||||
_conversationManager = param.thisObject()
|
conversationManager = ConversationManager(context, param.thisObject())
|
||||||
context.messagingBridge.triggerSessionStart()
|
context.messagingBridge.triggerSessionStart()
|
||||||
context.mainActivity?.takeIf { it.intent.getBooleanExtra(ReceiversConfig.MESSAGING_PREVIEW_EXTRA,false) }?.run {
|
context.mainActivity?.takeIf { it.intent.getBooleanExtra(ReceiversConfig.MESSAGING_PREVIEW_EXTRA,false) }?.run {
|
||||||
finishAndRemoveTask()
|
finishAndRemoveTask()
|
||||||
|
@ -14,6 +14,7 @@ import de.robv.android.xposed.XposedBridge
|
|||||||
import de.robv.android.xposed.XposedHelpers
|
import de.robv.android.xposed.XposedHelpers
|
||||||
import me.rhunk.snapenhance.common.data.ContentType
|
import me.rhunk.snapenhance.common.data.ContentType
|
||||||
import me.rhunk.snapenhance.common.data.MediaReferenceType
|
import me.rhunk.snapenhance.common.data.MediaReferenceType
|
||||||
|
import me.rhunk.snapenhance.common.data.MessageUpdate
|
||||||
import me.rhunk.snapenhance.common.data.NotificationType
|
import me.rhunk.snapenhance.common.data.NotificationType
|
||||||
import me.rhunk.snapenhance.common.data.download.SplitMediaAssetType
|
import me.rhunk.snapenhance.common.data.download.SplitMediaAssetType
|
||||||
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
|
||||||
@ -191,31 +192,26 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
|||||||
|
|
||||||
val conversationManager = context.feature(Messaging::class).conversationManager ?: return@subscribe
|
val conversationManager = context.feature(Messaging::class).conversationManager ?: return@subscribe
|
||||||
|
|
||||||
context.classCache.conversationManager.methods.first { it.name == "displayedMessages"}?.invoke(
|
conversationManager.displayedMessages(
|
||||||
conversationManager,
|
conversationId,
|
||||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
|
||||||
messageId,
|
messageId,
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
onResult = {
|
||||||
.override("onError") {
|
if (it != null) {
|
||||||
context.log.error("Failed to mark message as read: ${it.arg(0) as Any}")
|
context.log.error("Failed to mark conversation as read: $it")
|
||||||
context.shortToast("Failed to mark message as read")
|
context.shortToast("Failed to mark conversation as read")
|
||||||
}.build()
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val conversationMessage = context.database.getConversationMessageFromId(messageId) ?: return@subscribe
|
val conversationMessage = context.database.getConversationMessageFromId(messageId) ?: return@subscribe
|
||||||
|
|
||||||
if (conversationMessage.contentType == ContentType.SNAP.id) {
|
if (conversationMessage.contentType == ContentType.SNAP.id) {
|
||||||
context.classCache.conversationManager.methods.first { it.name == "updateMessage"}?.invoke(
|
conversationManager.updateMessage(conversationId, messageId, MessageUpdate.READ) {
|
||||||
conversationManager,
|
if (it != null) {
|
||||||
SnapUUID.fromString(conversationId).instanceNonNull(),
|
context.log.error("Failed to open snap: $it")
|
||||||
messageId,
|
context.shortToast("Failed to open snap")
|
||||||
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == "READ" },
|
}
|
||||||
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
}
|
||||||
.override("onError") {
|
|
||||||
context.log.error("Failed to open snap: ${it.arg(0) as Any}")
|
|
||||||
context.shortToast("Failed to open snap")
|
|
||||||
}.build()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
context.log.error("Failed to mark message as read", it)
|
context.log.error("Failed to mark message as read", it)
|
||||||
@ -346,8 +342,6 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
|||||||
override fun init() {
|
override fun init() {
|
||||||
setupBroadcastReceiverHook()
|
setupBroadcastReceiverHook()
|
||||||
|
|
||||||
val fetchConversationWithMessagesCallback = context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback")
|
|
||||||
|
|
||||||
notifyAsUserMethod.hook(HookStage.BEFORE) { param ->
|
notifyAsUserMethod.hook(HookStage.BEFORE) { param ->
|
||||||
val notificationData = NotificationData(param.argNullable(0), param.arg(1), param.arg(2), param.arg(3))
|
val notificationData = NotificationData(param.argNullable(0), param.arg(1), param.arg(2), param.arg(3))
|
||||||
|
|
||||||
@ -361,22 +355,16 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
|||||||
notificationType.contains(it)
|
notificationType.contains(it)
|
||||||
}) return@hook
|
}) return@hook
|
||||||
|
|
||||||
val conversationManager: Any = context.feature(Messaging::class).conversationManager ?: return@hook
|
|
||||||
|
|
||||||
synchronized(notificationDataQueue) {
|
synchronized(notificationDataQueue) {
|
||||||
notificationDataQueue[messageId.toLong()] = notificationData
|
notificationDataQueue[messageId.toLong()] = notificationData
|
||||||
}
|
}
|
||||||
|
|
||||||
val callback = CallbackBuilder(fetchConversationWithMessagesCallback)
|
context.feature(Messaging::class).conversationManager?.fetchConversationWithMessages(conversationId, onSuccess = { messages ->
|
||||||
.override("onFetchConversationWithMessagesComplete") { callbackParam ->
|
fetchMessagesResult(conversationId, messages)
|
||||||
val messageList = (callbackParam.arg(1) as List<Any>).map { msg -> Message(msg) }
|
}, onError = {
|
||||||
fetchMessagesResult(conversationId, messageList)
|
context.log.error("Failed to fetch conversation with messages: $it")
|
||||||
}
|
})
|
||||||
.override("onError") {
|
|
||||||
context.log.error("Failed to fetch message ${it.arg(0) as Any}")
|
|
||||||
}.build()
|
|
||||||
|
|
||||||
fetchConversationWithMessagesMethod.invoke(conversationManager, SnapUUID.fromString(conversationId).instanceNonNull(), callback)
|
|
||||||
param.setResult(null)
|
param.setResult(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,11 +5,10 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
|||||||
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
|
import me.rhunk.snapenhance.bridge.snapclient.MessagingBridge
|
||||||
import me.rhunk.snapenhance.bridge.snapclient.SessionStartListener
|
import me.rhunk.snapenhance.bridge.snapclient.SessionStartListener
|
||||||
import me.rhunk.snapenhance.bridge.snapclient.types.Message
|
import me.rhunk.snapenhance.bridge.snapclient.types.Message
|
||||||
|
import me.rhunk.snapenhance.common.data.MessageUpdate
|
||||||
import me.rhunk.snapenhance.core.ModContext
|
import me.rhunk.snapenhance.core.ModContext
|
||||||
import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
|
import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder
|
||||||
import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
|
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 {
|
fun me.rhunk.snapenhance.core.wrapper.impl.Message.toBridge(): Message {
|
||||||
@ -46,23 +45,14 @@ class CoreMessagingBridge(
|
|||||||
override fun fetchMessage(conversationId: String, clientMessageId: String): Message? {
|
override fun fetchMessage(conversationId: String, clientMessageId: String): Message? {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
val callback = CallbackBuilder(
|
conversationManager?.fetchMessage(
|
||||||
context.mappings.getMappedClass("callbacks", "FetchMessageCallback")
|
conversationId,
|
||||||
).override("onFetchMessageComplete") { param ->
|
|
||||||
val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(0)).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.toLong(),
|
clientMessageId.toLong(),
|
||||||
callback
|
onSuccess = {
|
||||||
)
|
continuation.resumeWith(Result.success(it.toBridge()))
|
||||||
|
},
|
||||||
|
onError = { continuation.resumeWith(Result.success(null)) }
|
||||||
|
) ?: continuation.resumeWith(Result.success(null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,26 +63,14 @@ class CoreMessagingBridge(
|
|||||||
): Message? {
|
): Message? {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
val callback = CallbackBuilder(
|
conversationManager?.fetchMessageByServerId(
|
||||||
context.mappings.getMappedClass("callbacks", "FetchMessageCallback")
|
conversationId,
|
||||||
).override("onFetchMessageComplete") { param ->
|
serverMessageId,
|
||||||
val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(1)).toBridge()
|
onSuccess = {
|
||||||
continuation.resumeWith(Result.success(message))
|
continuation.resumeWith(Result.success(it.toBridge()))
|
||||||
}
|
},
|
||||||
.override("onServerRequest", shouldUnhook = false) {}
|
onError = { continuation.resumeWith(Result.success(null)) }
|
||||||
.override("onError") {
|
) ?: continuation.resumeWith(Result.success(null))
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,26 +82,17 @@ class CoreMessagingBridge(
|
|||||||
): List<Message>? {
|
): List<Message>? {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
val callback = CallbackBuilder(
|
conversationManager?.fetchConversationWithMessagesPaginated(
|
||||||
context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback")
|
conversationId,
|
||||||
).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,
|
beforeMessageId,
|
||||||
limit,
|
limit,
|
||||||
callback
|
onSuccess = { messages ->
|
||||||
)
|
continuation.resumeWith(Result.success(messages.map { it.toBridge() }))
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
continuation.resumeWith(Result.success(null))
|
||||||
|
}
|
||||||
|
) ?: continuation.resumeWith(Result.success(null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,22 +104,14 @@ class CoreMessagingBridge(
|
|||||||
): String? {
|
): String? {
|
||||||
return runBlocking {
|
return runBlocking {
|
||||||
suspendCancellableCoroutine { continuation ->
|
suspendCancellableCoroutine { continuation ->
|
||||||
val callback = CallbackBuilder(
|
conversationManager?.updateMessage(
|
||||||
context.mappings.getMappedClass("callbacks", "Callback")
|
conversationId,
|
||||||
).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,
|
clientMessageId,
|
||||||
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == messageUpdate },
|
MessageUpdate.valueOf(messageUpdate),
|
||||||
callback
|
onResult = {
|
||||||
)
|
continuation.resumeWith(Result.success(it))
|
||||||
|
}
|
||||||
|
) ?: continuation.resumeWith(Result.success("ConversationManager is null"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,10 @@ class CallbackBuilder(
|
|||||||
|
|
||||||
fun build(): Any {
|
fun build(): Any {
|
||||||
//get the first param of the first constructor to get the class of the invoker
|
//get the first param of the first constructor to get the class of the invoker
|
||||||
val invokerClass: Class<*> = callbackClass.constructors[0].parameterTypes[0]
|
val rxEmitter: Class<*> = callbackClass.constructors[0].parameterTypes[0]
|
||||||
//get the invoker field based on the invoker class
|
//get the emitter field based on the class
|
||||||
val invokerField = callbackClass.fields.first { field: Field ->
|
val rxEmitterField = callbackClass.fields.first { field: Field ->
|
||||||
field.type.isAssignableFrom(invokerClass)
|
field.type.isAssignableFrom(rxEmitter)
|
||||||
}
|
}
|
||||||
//get the callback field based on the callback class
|
//get the callback field based on the callback class
|
||||||
val callbackInstance = createEmptyObject(callbackClass.constructors[0])!!
|
val callbackInstance = createEmptyObject(callbackClass.constructors[0])!!
|
||||||
@ -44,8 +44,8 @@ class CallbackBuilder(
|
|||||||
|
|
||||||
//default hook that unhooks the callback and returns null
|
//default hook that unhooks the callback and returns null
|
||||||
val defaultHook: (HookAdapter) -> Boolean = defaultHook@{
|
val defaultHook: (HookAdapter) -> Boolean = defaultHook@{
|
||||||
//checking invokerField ensure that's the callback was created by the CallbackBuilder
|
//ensure that's the callback was created by the CallbackBuilder
|
||||||
if (invokerField.get(it.thisObject()) != null) return@defaultHook false
|
if (rxEmitterField.get(it.thisObject()) != null) return@defaultHook false
|
||||||
if ((it.thisObject() as Any).hashCode() != callbackInstanceHashCode) return@defaultHook false
|
if ((it.thisObject() as Any).hashCode() != callbackInstanceHashCode) return@defaultHook false
|
||||||
it.setResult(null)
|
it.setResult(null)
|
||||||
true
|
true
|
||||||
|
@ -1,119 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.core.util
|
|
||||||
|
|
||||||
import java.lang.reflect.Field
|
|
||||||
import java.lang.reflect.Method
|
|
||||||
import java.util.Arrays
|
|
||||||
import java.util.Objects
|
|
||||||
|
|
||||||
object ReflectionHelper {
|
|
||||||
/**
|
|
||||||
* Searches for a field with a class that has a method with the specified name
|
|
||||||
*/
|
|
||||||
fun searchFieldWithClassMethod(clazz: Class<*>, methodName: String): Field? {
|
|
||||||
return clazz.declaredFields.firstOrNull { f: Field? ->
|
|
||||||
try {
|
|
||||||
return@firstOrNull Arrays.stream(
|
|
||||||
f!!.type.declaredMethods
|
|
||||||
).anyMatch { method: Method -> method.name == methodName }
|
|
||||||
} catch (e: Exception) {
|
|
||||||
return@firstOrNull false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchFieldByType(clazz: Class<*>, type: Class<*>): Field? {
|
|
||||||
return clazz.declaredFields.firstOrNull { f: Field? -> f!!.type == type }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchFieldTypeInSuperClasses(clazz: Class<*>, type: Class<*>): Field? {
|
|
||||||
val field = searchFieldByType(clazz, type)
|
|
||||||
if (field != null) {
|
|
||||||
return field
|
|
||||||
}
|
|
||||||
val superclass = clazz.superclass
|
|
||||||
return superclass?.let { searchFieldTypeInSuperClasses(it, type) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchFieldStartsWithToString(
|
|
||||||
clazz: Class<*>,
|
|
||||||
instance: Any,
|
|
||||||
toString: String?
|
|
||||||
): Field? {
|
|
||||||
return clazz.declaredFields.firstOrNull { f: Field ->
|
|
||||||
try {
|
|
||||||
f.isAccessible = true
|
|
||||||
return@firstOrNull Objects.requireNonNull(f[instance]).toString()
|
|
||||||
.startsWith(
|
|
||||||
toString!!
|
|
||||||
)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
return@firstOrNull false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun searchFieldContainsToString(
|
|
||||||
clazz: Class<*>,
|
|
||||||
instance: Any?,
|
|
||||||
toString: String?
|
|
||||||
): Field? {
|
|
||||||
return clazz.declaredFields.firstOrNull { f: Field ->
|
|
||||||
try {
|
|
||||||
f.isAccessible = true
|
|
||||||
return@firstOrNull Objects.requireNonNull(f[instance]).toString()
|
|
||||||
.contains(toString!!)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
return@firstOrNull false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchFirstFieldTypeInClassRecursive(clazz: Class<*>, type: Class<*>): Field? {
|
|
||||||
return clazz.declaredFields.firstOrNull {
|
|
||||||
val field = searchFieldByType(it.type, type)
|
|
||||||
return@firstOrNull field != null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for a field with a class that has a method with the specified return type
|
|
||||||
*/
|
|
||||||
fun searchMethodWithReturnType(clazz: Class<*>, returnType: Class<*>): Method? {
|
|
||||||
return clazz.declaredMethods.first { m: Method -> m.returnType == returnType }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Searches for a field with a class that has a method with the specified return type and parameter types
|
|
||||||
*/
|
|
||||||
fun searchMethodWithParameterAndReturnType(
|
|
||||||
aClass: Class<*>,
|
|
||||||
returnType: Class<*>,
|
|
||||||
vararg parameters: Class<*>
|
|
||||||
): Method? {
|
|
||||||
return aClass.declaredMethods.firstOrNull { m: Method ->
|
|
||||||
if (m.returnType != returnType) {
|
|
||||||
return@firstOrNull false
|
|
||||||
}
|
|
||||||
val parameterTypes = m.parameterTypes
|
|
||||||
if (parameterTypes.size != parameters.size) {
|
|
||||||
return@firstOrNull false
|
|
||||||
}
|
|
||||||
for (i in parameterTypes.indices) {
|
|
||||||
if (parameterTypes[i] != parameters[i]) {
|
|
||||||
return@firstOrNull false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDeclaredFieldsRecursively(clazz: Class<*>): List<Field> {
|
|
||||||
val fields = clazz.declaredFields.toMutableList()
|
|
||||||
val superclass = clazz.superclass
|
|
||||||
if (superclass != null) {
|
|
||||||
fields.addAll(getDeclaredFieldsRecursively(superclass))
|
|
||||||
}
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,105 @@
|
|||||||
|
package me.rhunk.snapenhance.core.wrapper.impl
|
||||||
|
|
||||||
|
import me.rhunk.snapenhance.common.data.MessageUpdate
|
||||||
|
import me.rhunk.snapenhance.core.ModContext
|
||||||
|
import me.rhunk.snapenhance.core.util.CallbackBuilder
|
||||||
|
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
||||||
|
|
||||||
|
typealias CallbackResult = (error: String?) -> Unit
|
||||||
|
|
||||||
|
class ConversationManager(val context: ModContext, obj: Any) : AbstractWrapper(obj) {
|
||||||
|
private fun findMethodByName(name: String) = context.classCache.conversationManager.declaredMethods.find { it.name == name } ?: throw RuntimeException("Could not find method $name")
|
||||||
|
|
||||||
|
private val updateMessageMethod by lazy { findMethodByName("updateMessage") }
|
||||||
|
private val fetchConversationWithMessagesPaginatedMethod by lazy { findMethodByName("fetchConversationWithMessagesPaginated") }
|
||||||
|
private val fetchConversationWithMessagesMethod by lazy { findMethodByName("fetchConversationWithMessages") }
|
||||||
|
private val fetchMessageByServerId by lazy { findMethodByName("fetchMessageByServerId") }
|
||||||
|
private val displayedMessagesMethod by lazy { findMethodByName("displayedMessages") }
|
||||||
|
private val fetchMessage by lazy { findMethodByName("fetchMessage") }
|
||||||
|
|
||||||
|
|
||||||
|
fun updateMessage(conversationId: String, messageId: Long, action: MessageUpdate, onResult: CallbackResult = {}) {
|
||||||
|
updateMessageMethod.invoke(
|
||||||
|
instanceNonNull(),
|
||||||
|
SnapUUID.fromString(conversationId).instanceNonNull(),
|
||||||
|
messageId,
|
||||||
|
context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == action.toString() },
|
||||||
|
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
||||||
|
.override("onSuccess") { onResult(null) }
|
||||||
|
.override("onError") { onResult(it.arg<Any>(0).toString()) }.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchConversationWithMessagesPaginated(conversationId: String, lastMessageId: Long, amount: Int, onSuccess: (message: List<Message>) -> Unit, onError: (error: String) -> Unit) {
|
||||||
|
val callback = CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"))
|
||||||
|
.override("onFetchConversationWithMessagesComplete") { param ->
|
||||||
|
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
||||||
|
}
|
||||||
|
.override("onServerRequest", shouldUnhook = false) {}
|
||||||
|
.override("onError") {
|
||||||
|
onError(it.arg<Any>(0).toString())
|
||||||
|
}.build()
|
||||||
|
fetchConversationWithMessagesPaginatedMethod.invoke(instanceNonNull(), conversationId.toSnapUUID().instanceNonNull(), lastMessageId, amount, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchConversationWithMessages(conversationId: String, onSuccess: (List<Message>) -> Unit, onError: (error: String) -> Unit) {
|
||||||
|
fetchConversationWithMessagesMethod.invoke(
|
||||||
|
instanceNonNull(),
|
||||||
|
conversationId.toSnapUUID().instanceNonNull(),
|
||||||
|
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback"))
|
||||||
|
.override("onFetchConversationWithMessagesComplete") { param ->
|
||||||
|
onSuccess(param.arg<List<*>>(1).map { Message(it) })
|
||||||
|
}
|
||||||
|
.override("onServerRequest", shouldUnhook = false) {}
|
||||||
|
.override("onError") {
|
||||||
|
onError(it.arg<Any>(0).toString())
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun displayedMessages(conversationId: String, messageId: Long, onResult: CallbackResult = {}) {
|
||||||
|
displayedMessagesMethod.invoke(
|
||||||
|
instanceNonNull(),
|
||||||
|
conversationId.toSnapUUID(),
|
||||||
|
messageId,
|
||||||
|
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
|
||||||
|
.override("onSuccess") { onResult(null) }
|
||||||
|
.override("onError") { onResult(it.arg<Any>(0).toString()) }.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchMessage(conversationId: String, messageId: Long, onSuccess: (Message) -> Unit, onError: (error: String) -> Unit = {}) {
|
||||||
|
fetchMessage.invoke(
|
||||||
|
instanceNonNull(),
|
||||||
|
conversationId.toSnapUUID().instanceNonNull(),
|
||||||
|
messageId,
|
||||||
|
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
|
||||||
|
.override("onSuccess") { param ->
|
||||||
|
onSuccess(Message(param.arg(0)))
|
||||||
|
}
|
||||||
|
.override("onServerRequest", shouldUnhook = false) {}
|
||||||
|
.override("onError") {
|
||||||
|
onError(it.arg<Any>(0).toString())
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fetchMessageByServerId(conversationId: String, serverMessageId: String, onSuccess: (Message) -> Unit, onError: (error: String) -> Unit) {
|
||||||
|
val serverMessageIdentifier = context.classCache.serverMessageIdentifier
|
||||||
|
.getConstructor(context.classCache.snapUUID, Long::class.javaPrimitiveType)
|
||||||
|
.newInstance(conversationId.toSnapUUID().instanceNonNull(), serverMessageId.toLong())
|
||||||
|
|
||||||
|
fetchMessageByServerId.invoke(
|
||||||
|
instanceNonNull(),
|
||||||
|
serverMessageIdentifier,
|
||||||
|
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
|
||||||
|
.override("onFetchMessageComplete") { param ->
|
||||||
|
onSuccess(Message(param.arg(1)))
|
||||||
|
}
|
||||||
|
.override("onServerRequest", shouldUnhook = false) {}
|
||||||
|
.override("onError") {
|
||||||
|
onError(it.arg<Any>(0).toString())
|
||||||
|
}.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,8 @@ import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
|||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
fun String.toSnapUUID() = SnapUUID.fromString(this)
|
||||||
|
|
||||||
class SnapUUID(obj: Any?) : AbstractWrapper(obj) {
|
class SnapUUID(obj: Any?) : AbstractWrapper(obj) {
|
||||||
private val uuidString by lazy { toUUID().toString() }
|
private val uuidString by lazy { toUUID().toString() }
|
||||||
|
|
||||||
|
@ -1,21 +1,19 @@
|
|||||||
package me.rhunk.snapenhance.core.wrapper.impl.media.opera
|
package me.rhunk.snapenhance.core.wrapper.impl.media.opera
|
||||||
|
|
||||||
import me.rhunk.snapenhance.core.util.ReflectionHelper
|
import me.rhunk.snapenhance.common.util.ktx.findFieldsToString
|
||||||
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
||||||
|
|
||||||
class Layer(obj: Any?) : AbstractWrapper(obj) {
|
class Layer(obj: Any?) : AbstractWrapper(obj) {
|
||||||
val paramMap: ParamMap
|
val paramMap: ParamMap
|
||||||
get() {
|
get() {
|
||||||
val layerControllerField = ReflectionHelper.searchFieldContainsToString(
|
val layerControllerField = instanceNonNull()::class.java.findFieldsToString(instance, once = true) { _, value ->
|
||||||
instanceNonNull()::class.java,
|
value.contains("OperaPageModel")
|
||||||
instance,
|
}.firstOrNull() ?: throw RuntimeException("Could not find layerController field")
|
||||||
"OperaPageModel"
|
|
||||||
)!!
|
val paramsMapHashMap = layerControllerField.type.findFieldsToString(layerControllerField[instance], once = true) { _, value ->
|
||||||
|
value.contains("OperaPageModel")
|
||||||
|
}.firstOrNull() ?: throw RuntimeException("Could not find paramsMap field")
|
||||||
|
|
||||||
val paramsMapHashMap = ReflectionHelper.searchFieldStartsWithToString(
|
|
||||||
layerControllerField.type,
|
|
||||||
layerControllerField[instance] as Any, "OperaPageModel"
|
|
||||||
)!!
|
|
||||||
return ParamMap(paramsMapHashMap[layerControllerField[instance]]!!)
|
return ParamMap(paramsMapHashMap[layerControllerField[instance]]!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
package me.rhunk.snapenhance.core.wrapper.impl.media.opera
|
|
||||||
|
|
||||||
import de.robv.android.xposed.XposedHelpers
|
|
||||||
import me.rhunk.snapenhance.core.util.ReflectionHelper
|
|
||||||
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
|
||||||
import java.lang.reflect.Field
|
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
|
||||||
|
|
||||||
class LayerController(obj: Any?) : AbstractWrapper(obj) {
|
|
||||||
val paramMap: ParamMap
|
|
||||||
get() {
|
|
||||||
val paramMapField: Field = ReflectionHelper.searchFieldTypeInSuperClasses(
|
|
||||||
instanceNonNull()::class.java,
|
|
||||||
ConcurrentHashMap::class.java
|
|
||||||
) ?: throw RuntimeException("Could not find paramMap field")
|
|
||||||
return ParamMap(XposedHelpers.getObjectField(instance, paramMapField.name))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
package me.rhunk.snapenhance.core.wrapper.impl.media.opera
|
package me.rhunk.snapenhance.core.wrapper.impl.media.opera
|
||||||
|
|
||||||
import me.rhunk.snapenhance.core.util.ReflectionHelper
|
import me.rhunk.snapenhance.common.util.ktx.findFields
|
||||||
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
import me.rhunk.snapenhance.core.util.ktx.getObjectField
|
||||||
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
|
||||||
import java.lang.reflect.Field
|
import java.lang.reflect.Field
|
||||||
@ -9,10 +9,9 @@ import java.util.concurrent.ConcurrentHashMap
|
|||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
class ParamMap(obj: Any?) : AbstractWrapper(obj) {
|
class ParamMap(obj: Any?) : AbstractWrapper(obj) {
|
||||||
private val paramMapField: Field by lazy {
|
private val paramMapField: Field by lazy {
|
||||||
ReflectionHelper.searchFieldTypeInSuperClasses(
|
instanceNonNull()::class.java.findFields(once = true) {
|
||||||
instanceNonNull().javaClass,
|
it.type == ConcurrentHashMap::class.java
|
||||||
ConcurrentHashMap::class.java
|
}.firstOrNull() ?: throw RuntimeException("Could not find paramMap field")
|
||||||
)!!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val concurrentHashMap: ConcurrentHashMap<Any, Any>
|
val concurrentHashMap: ConcurrentHashMap<Any, Any>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user