fix(core): notifications

This commit is contained in:
rhunk
2023-11-05 02:32:56 +01:00
parent f1a368d44e
commit 517aa82157
4 changed files with 222 additions and 189 deletions

View File

@ -12,6 +12,10 @@ import android.os.Bundle
import android.os.UserHandle import android.os.UserHandle
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
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.MessageUpdate
@ -33,8 +37,25 @@ import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.util.media.PreviewUtils import me.rhunk.snapenhance.core.util.media.PreviewUtils
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 me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
import kotlin.coroutines.suspendCoroutine
class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.INIT_SYNC) { class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.INIT_SYNC) {
inner class NotificationData(
val tag: String?,
val id: Int,
var notification: Notification,
val userHandle: UserHandle
) {
fun send() {
XposedBridge.invokeOriginalMethod(notifyAsUserMethod, notificationManager, arrayOf(
tag, id, notification, userHandle
))
}
fun copy(tag: String? = this.tag, id: Int = this.id, notification: Notification = this.notification, userHandle: UserHandle = this.userHandle) =
NotificationData(tag, id, notification, userHandle)
}
companion object{ companion object{
const val ACTION_REPLY = "me.rhunk.snapenhance.action.notification.REPLY" const val ACTION_REPLY = "me.rhunk.snapenhance.action.notification.REPLY"
const val ACTION_DOWNLOAD = "me.rhunk.snapenhance.action.notification.DOWNLOAD" const val ACTION_DOWNLOAD = "me.rhunk.snapenhance.action.notification.DOWNLOAD"
@ -42,9 +63,10 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
const val SNAPCHAT_NOTIFICATION_GROUP = "snapchat_notification_group" const val SNAPCHAT_NOTIFICATION_GROUP = "snapchat_notification_group"
} }
private val notificationDataQueue = mutableMapOf<Long, NotificationData>() // messageId => notification @OptIn(ExperimentalCoroutinesApi::class)
private val cachedMessages = mutableMapOf<String, MutableList<String>>() // conversationId => cached messages private val coroutineDispatcher = Dispatchers.IO.limitedParallelism(1)
private val notificationIdMap = mutableMapOf<Int, String>() // notificationId => conversationId private val cachedMessages = mutableMapOf<String, MutableMap<Long, String>>() // conversationId => orderKey, message
private val sentNotifications = mutableMapOf<Int, String>() // notificationId => conversationId
private val notifyAsUserMethod by lazy { private val notifyAsUserMethod by lazy {
XposedHelpers.findMethodExact( XposedHelpers.findMethodExact(
@ -56,10 +78,6 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
) )
} }
private val fetchConversationWithMessagesMethod by lazy {
context.classCache.conversationManager.methods.first { it.name == "fetchConversationWithMessages"}
}
private val notificationManager by lazy { private val notificationManager by lazy {
context.androidContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.androidContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
} }
@ -70,11 +88,17 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
context.config.messaging.betterNotifications.get() context.config.messaging.betterNotifications.get()
} }
private fun newNotificationBuilder(notification: Notification) = XposedHelpers.newInstance(
Notification.Builder::class.java,
context.androidContext,
notification
) as Notification.Builder
private fun setNotificationText(notification: Notification, conversationId: String) { private fun setNotificationText(notification: Notification, conversationId: String) {
val messageText = StringBuilder().apply { val messageText = StringBuilder().apply {
cachedMessages.computeIfAbsent(conversationId) { mutableListOf() }.forEach { cachedMessages.computeIfAbsent(conversationId) { sortedMapOf() }.forEach {
if (isNotEmpty()) append("\n") if (isNotEmpty()) append("\n")
append(it) append(it.value)
} }
}.toString() }.toString()
@ -91,14 +115,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
} }
} }
private fun setupNotificationActionButtons(contentType: ContentType, conversationId: String, messageId: Long, notificationData: NotificationData) { private fun setupNotificationActionButtons(contentType: ContentType, conversationId: String, message: Message, notificationData: NotificationData) {
val notificationBuilder = XposedHelpers.newInstance(
Notification.Builder::class.java,
context.androidContext,
notificationData.notification
) as Notification.Builder
val actions = mutableListOf<Notification.Action>() val actions = mutableListOf<Notification.Action>()
actions.addAll(notificationData.notification.actions) actions.addAll(notificationData.notification.actions)
@ -108,7 +125,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
val intent = SnapWidgetBroadcastReceiverHelper.create(remoteAction) { val intent = SnapWidgetBroadcastReceiverHelper.create(remoteAction) {
putExtra("conversation_id", conversationId) putExtra("conversation_id", conversationId)
putExtra("notification_id", notificationData.id) putExtra("notification_id", notificationData.id)
putExtra("message_id", messageId) putExtra("client_message_id", message.messageDescriptor!!.messageId!!)
} }
val action = Notification.Action.Builder(null, title, PendingIntent.getBroadcast( val action = Notification.Action.Builder(null, title, PendingIntent.getBroadcast(
@ -137,7 +154,9 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
betterNotificationFilter.contains("mark_as_read_button") betterNotificationFilter.contains("mark_as_read_button")
}) {} }) {}
notificationBuilder.setActions(*actions.toTypedArray()) val notificationBuilder = newNotificationBuilder(notificationData.notification).apply {
setActions(*actions.toTypedArray())
}
notificationData.notification = notificationBuilder.build() notificationData.notification = notificationBuilder.build()
} }
@ -145,15 +164,26 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
context.event.subscribe(SnapWidgetBroadcastReceiveEvent::class) { event -> context.event.subscribe(SnapWidgetBroadcastReceiveEvent::class) { event ->
val intent = event.intent ?: return@subscribe val intent = event.intent ?: return@subscribe
val conversationId = intent.getStringExtra("conversation_id") ?: return@subscribe val conversationId = intent.getStringExtra("conversation_id") ?: return@subscribe
val messageId = intent.getLongExtra("message_id", -1) val clientMessageId = intent.getLongExtra("client_message_id", -1)
val notificationId = intent.getIntExtra("notification_id", -1) val notificationId = intent.getIntExtra("notification_id", -1)
val updateNotification: (Int, (Notification) -> Unit) -> Unit = { id, notificationBuilder -> val updateNotification: (Int, (Notification) -> Unit) -> Unit = { id, notificationBuilder ->
notificationManager.activeNotifications.firstOrNull { it.id == id }?.let { notificationManager.activeNotifications.firstOrNull { it.id == id }?.let {
notificationBuilder(it.notification) notificationBuilder(it.notification)
XposedBridge.invokeOriginalMethod(notifyAsUserMethod, notificationManager, arrayOf( NotificationData(it.tag, it.id, it.notification, it.user).send()
it.tag, it.id, it.notification, it.user }
)) }
suspend fun appendNotificationText(input: String) {
cachedMessages.computeIfAbsent(conversationId) { sortedMapOf() }.let {
it[(it.keys.lastOrNull() ?: 0) + 1L] = input
}
withContext(Dispatchers.Main) {
updateNotification(notificationId) { notification ->
notification.flags = notification.flags or Notification.FLAG_ONLY_ALERT_ONCE
setNotificationText(notification, conversationId)
}
} }
} }
@ -161,23 +191,22 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
ACTION_REPLY -> { ACTION_REPLY -> {
val input = RemoteInput.getResultsFromIntent(intent).getCharSequence("chat_reply_input") val input = RemoteInput.getResultsFromIntent(intent).getCharSequence("chat_reply_input")
.toString() .toString()
val myUser = context.database.myUserId.let { context.database.getFriendInfo(it) } ?: return@subscribe
context.database.myUserId.let { context.database.getFriendInfo(it) }?.let { myUser -> context.messageSender.sendChatMessage(listOf(SnapUUID.fromString(conversationId)), input, onError = {
cachedMessages.computeIfAbsent(conversationId) { mutableListOf() }.add("${myUser.displayName}: $input") context.longToast("Failed to send message: $it")
context.coroutineScope.launch(coroutineDispatcher) {
updateNotification(notificationId) { notification -> appendNotificationText("Failed to send message: $it")
notification.flags = notification.flags or Notification.FLAG_ONLY_ALERT_ONCE
setNotificationText(notification, conversationId)
} }
}, onSuccess = {
context.messageSender.sendChatMessage(listOf(SnapUUID.fromString(conversationId)), input, onError = { context.coroutineScope.launch(coroutineDispatcher) {
context.longToast("Failed to send message: $it") appendNotificationText("${myUser.displayName ?: myUser.mutableUsername}: $input")
}) }
} })
} }
ACTION_DOWNLOAD -> { ACTION_DOWNLOAD -> {
runCatching { runCatching {
context.feature(MediaDownloader::class).downloadMessageId(messageId, isPreview = false) context.feature(MediaDownloader::class).downloadMessageId(clientMessageId, isPreview = false)
}.onFailure { }.onFailure {
context.longToast(it) context.longToast(it)
} }
@ -193,7 +222,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
conversationManager.displayedMessages( conversationManager.displayedMessages(
conversationId, conversationId,
messageId, clientMessageId,
onResult = { onResult = {
if (it != null) { if (it != null) {
context.log.error("Failed to mark conversation as read: $it") context.log.error("Failed to mark conversation as read: $it")
@ -202,10 +231,10 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
} }
) )
val conversationMessage = context.database.getConversationMessageFromId(messageId) ?: return@subscribe val conversationMessage = context.database.getConversationMessageFromId(clientMessageId) ?: return@subscribe
if (conversationMessage.contentType == ContentType.SNAP.id) { if (conversationMessage.contentType == ContentType.SNAP.id) {
conversationManager.updateMessage(conversationId, messageId, MessageUpdate.READ) { conversationManager.updateMessage(conversationId, clientMessageId, MessageUpdate.READ) {
if (it != null) { if (it != null) {
context.log.error("Failed to open snap: $it") context.log.error("Failed to open snap: $it")
context.shortToast("Failed to open snap") context.shortToast("Failed to open snap")
@ -225,117 +254,111 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
} }
} }
private fun fetchMessagesResult(conversationId: String, messages: List<Message>) { private fun sendNotification(message: Message, notificationData: NotificationData, forceCreate: Boolean) {
val sendNotificationData = { notificationData: NotificationData, forceCreate: Boolean -> val conversationId = message.messageDescriptor?.conversationId.toString()
val notificationId = if (forceCreate) System.nanoTime().toInt() else notificationData.id val notificationId = if (forceCreate) System.nanoTime().toInt() else notificationData.id
notificationIdMap.computeIfAbsent(notificationId) { conversationId } sentNotifications.computeIfAbsent(notificationId) { conversationId }
if (betterNotificationFilter.contains("group")) {
runCatching {
notificationData.notification.setObjectField("mGroupKey", SNAPCHAT_NOTIFICATION_GROUP)
val summaryNotification = Notification.Builder(context.androidContext, notificationData.notification.channelId) if (betterNotificationFilter.contains("group")) {
.setSmallIcon(notificationData.notification.smallIcon) runCatching {
.setGroup(SNAPCHAT_NOTIFICATION_GROUP) if (notificationManager.activeNotifications.firstOrNull {
.setGroupSummary(true) it.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0
.setAutoCancel(true) } == null) {
.setOnlyAlertOnce(true) notificationManager.notify(
.build() notificationData.tag,
System.nanoTime().toInt(),
Notification.Builder(context.androidContext, notificationData.notification.channelId)
.setSmallIcon(notificationData.notification.smallIcon)
.setGroup(SNAPCHAT_NOTIFICATION_GROUP)
.setGroupSummary(true)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.build()
)
}
}.onFailure {
context.log.warn("Failed to set notification group key: ${it.stackTraceToString()}", featureKey)
}
}
if (notificationManager.activeNotifications.firstOrNull { it.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0 } == null) { notificationData.copy(id = notificationId).also {
notificationManager.notify(notificationData.tag, notificationData.id, summaryNotification) setupNotificationActionButtons(message.messageContent!!.contentType!!, conversationId, message, it)
}.send()
}
private fun onMessageReceived(data: NotificationData, message: Message) {
val conversationId = message.messageDescriptor?.conversationId.toString()
val orderKey = message.orderKey ?: return
val senderUsername by lazy {
context.database.getFriendInfo(message.senderId.toString())?.let {
it.displayName ?: it.mutableUsername
} ?: "Unknown"
}
val formatUsername: (String) -> String = { "$senderUsername: $it" }
val notificationCache = cachedMessages.let { it.computeIfAbsent(conversationId) { sortedMapOf() } }
val appendNotifications: () -> Unit = { setNotificationText(data.notification, conversationId)}
when (val contentType = message.messageContent!!.contentType) {
ContentType.NOTE -> {
notificationCache[orderKey] = formatUsername("sent audio note")
appendNotifications()
}
ContentType.CHAT -> {
ProtoReader(message.messageContent!!.content!!).getString(2, 1)?.trim()?.let {
notificationCache[orderKey] = formatUsername(it)
}
appendNotifications()
}
ContentType.SNAP, ContentType.EXTERNAL_MEDIA -> {
val mediaReferences = MessageDecoder.getMediaReferences(
messageContent = context.gson.toJsonTree(message.messageContent!!.instanceNonNull())
)
val mediaReferenceKeys = mediaReferences.map { reference ->
reference.asJsonObject.getAsJsonArray("mContentObject").map { it.asByte }.toByteArray()
}
MessageDecoder.decode(message.messageContent!!).firstOrNull()?.also { media ->
val mediaType = MediaReferenceType.valueOf(mediaReferences.first().asJsonObject["mMediaType"].asString)
runCatching {
val downloadedMedia = RemoteMediaResolver.downloadBoltMedia(mediaReferenceKeys.first(), decryptionCallback = {
media.attachmentInfo?.encryption?.decryptInputStream(it) ?: it
}) ?: throw Throwable("Unable to download media")
val downloadedMedias = mutableMapOf<SplitMediaAssetType, ByteArray>()
MediaDownloaderHelper.getSplitElements(downloadedMedia.inputStream()) { type, inputStream ->
downloadedMedias[type] = inputStream.readBytes()
}
var bitmapPreview = PreviewUtils.createPreview(downloadedMedias[SplitMediaAssetType.ORIGINAL]!!, mediaType.name.contains("VIDEO"))!!
downloadedMedias[SplitMediaAssetType.OVERLAY]?.let {
bitmapPreview = PreviewUtils.mergeBitmapOverlay(bitmapPreview, BitmapFactory.decodeByteArray(it, 0, it.size))
}
val notificationBuilder = newNotificationBuilder(data.notification).apply {
setLargeIcon(bitmapPreview)
style = Notification.BigPictureStyle().bigPicture(bitmapPreview).bigLargeIcon(null as Bitmap?)
}
sendNotification(message, data.copy(notification = notificationBuilder.build()), true)
return
}.onFailure {
context.log.error("Failed to send preview notification", it)
} }
}.onFailure {
context.log.warn("Failed to set notification group key: ${it.stackTraceToString()}", featureKey)
} }
} }
else -> {
XposedBridge.invokeOriginalMethod(notifyAsUserMethod, notificationManager, arrayOf( notificationCache[orderKey] = formatUsername("sent ${contentType?.name?.lowercase()}")
notificationData.tag, if (forceCreate) System.nanoTime().toInt() else notificationData.id, notificationData.notification, notificationData.userHandle appendNotifications()
)) }
} }
synchronized(notificationDataQueue) { sendNotification(message, data, false)
notificationDataQueue.entries.onEach { (messageId, notificationData) ->
val snapMessage = messages.firstOrNull { message -> message.orderKey == messageId } ?: return
val senderUsername by lazy {
context.database.getFriendInfo(snapMessage.senderId.toString())?.let {
it.displayName ?: it.mutableUsername
}
}
val contentType = snapMessage.messageContent!!.contentType ?: return@onEach
val contentData = snapMessage.messageContent!!.content!!
val formatUsername: (String) -> String = { "$senderUsername: $it" }
val notificationCache = cachedMessages.let { it.computeIfAbsent(conversationId) { mutableListOf() } }
val appendNotifications: () -> Unit = { setNotificationText(notificationData.notification, conversationId)}
setupNotificationActionButtons(contentType, conversationId, snapMessage.messageDescriptor!!.messageId!!, notificationData)
when (contentType) {
ContentType.NOTE -> {
notificationCache.add(formatUsername("sent audio note"))
appendNotifications()
}
ContentType.CHAT -> {
ProtoReader(contentData).getString(2, 1)?.trim()?.let {
notificationCache.add(formatUsername(it))
}
appendNotifications()
}
ContentType.SNAP, ContentType.EXTERNAL_MEDIA -> {
val mediaReferences = MessageDecoder.getMediaReferences(
messageContent = context.gson.toJsonTree(snapMessage.messageContent!!.instanceNonNull())
)
val mediaReferenceKeys = mediaReferences.map { reference ->
reference.asJsonObject.getAsJsonArray("mContentObject").map { it.asByte }.toByteArray()
}
MessageDecoder.decode(snapMessage.messageContent!!).firstOrNull()?.also { media ->
val mediaType = MediaReferenceType.valueOf(mediaReferences.first().asJsonObject["mMediaType"].asString)
runCatching {
val downloadedMedia = RemoteMediaResolver.downloadBoltMedia(mediaReferenceKeys.first(), decryptionCallback = {
media.attachmentInfo?.encryption?.decryptInputStream(it) ?: it
}) ?: throw Throwable("Unable to download media")
val downloadedMedias = mutableMapOf<SplitMediaAssetType, ByteArray>()
MediaDownloaderHelper.getSplitElements(downloadedMedia.inputStream()) { type, inputStream ->
downloadedMedias[type] = inputStream.readBytes()
}
var bitmapPreview = PreviewUtils.createPreview(downloadedMedias[SplitMediaAssetType.ORIGINAL]!!, mediaType.name.contains("VIDEO"))!!
downloadedMedias[SplitMediaAssetType.OVERLAY]?.let {
bitmapPreview = PreviewUtils.mergeBitmapOverlay(bitmapPreview, BitmapFactory.decodeByteArray(it, 0, it.size))
}
val notificationBuilder = XposedHelpers.newInstance(
Notification.Builder::class.java,
context.androidContext,
notificationData.notification
) as Notification.Builder
notificationBuilder.setLargeIcon(bitmapPreview)
notificationBuilder.style = Notification.BigPictureStyle().bigPicture(bitmapPreview).bigLargeIcon(null as Bitmap?)
sendNotificationData(notificationData.copy(notification = notificationBuilder.build()), true)
return@onEach
}.onFailure {
context.log.error("Failed to send preview notification", it)
}
}
}
else -> {
notificationCache.add(formatUsername("sent ${contentType.name.lowercase()}"))
appendNotifications()
}
}
sendNotificationData(notificationData, false)
}.clear()
}
} }
override fun init() { override fun init() {
@ -343,39 +366,53 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
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))
val extras = notificationData.notification.extras.getBundle("system_notification_extras")?: return@hook
val extras: Bundle = notificationData.notification.extras.getBundle("system_notification_extras")?: return@hook if (betterNotificationFilter.contains("group")) {
notificationData.notification.setObjectField("mGroupKey", SNAPCHAT_NOTIFICATION_GROUP)
val messageId = extras.getString("message_id") ?: return@hook
val notificationType = extras.getString("notification_type") ?: return@hook
val conversationId = extras.getString("conversation_id") ?: return@hook
if (betterNotificationFilter.map { it.uppercase() }.none {
notificationType.contains(it)
}) return@hook
synchronized(notificationDataQueue) {
notificationDataQueue[messageId.toLong()] = notificationData
} }
context.feature(Messaging::class).conversationManager?.fetchConversationWithMessages(conversationId, onSuccess = { messages -> val conversationId = extras.getString("conversation_id").also { id ->
fetchMessagesResult(conversationId, messages) sentNotifications.computeIfAbsent(notificationData.id) { id ?: "" }
}, onError = { } ?: return@hook
context.log.error("Failed to fetch conversation with messages: $it")
}) val serverMessageId = extras.getString("message_id") ?: return@hook
val notificationType = extras.getString("notification_type") ?: return@hook
if (betterNotificationFilter.none { notificationType.contains(it, ignoreCase = true) }) return@hook
param.setResult(null) param.setResult(null)
val conversationManager = context.feature(Messaging::class).conversationManager ?: return@hook
context.coroutineScope.launch(coroutineDispatcher) {
suspendCoroutine { continuation ->
conversationManager.fetchMessageByServerId(conversationId, serverMessageId, onSuccess = {
onMessageReceived(notificationData, it)
continuation.resumeWith(Result.success(Unit))
}, onError = {
context.log.error("Failed to fetch message id ${serverMessageId}: $it")
continuation.resumeWith(Result.success(Unit))
})
}
}
} }
XposedHelpers.findMethodExact( NotificationManager::class.java.declaredMethods.find {
NotificationManager::class.java, it.name == "cancelAsUser"
"cancelAsUser", String::class.java, }?.hook(HookStage.AFTER) { param ->
Int::class.javaPrimitiveType,
UserHandle::class.java
).hook(HookStage.BEFORE) { param ->
val notificationId = param.arg<Int>(1) val notificationId = param.arg<Int>(1)
notificationIdMap[notificationId]?.let {
cachedMessages[it]?.clear() context.coroutineScope.launch(coroutineDispatcher) {
sentNotifications[notificationId]?.let {
cachedMessages[it]?.clear()
}
sentNotifications.remove(notificationId)
}
notificationManager.activeNotifications.let { notifications ->
if (notifications.all { it.notification.flags and Notification.FLAG_GROUP_SUMMARY != 0 }) {
notifications.forEach { param.invokeOriginal(arrayOf(it.tag, it.id, it.user)) }
}
} }
} }
@ -398,11 +435,4 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
} }
} }
} }
data class NotificationData(
val tag: String?,
val id: Int,
var notification: Notification,
val userHandle: UserHandle
)
} }

View File

@ -73,15 +73,16 @@ class CallbackBuilder(
//compute the args for the constructor with null or default primitive values //compute the args for the constructor with null or default primitive values
val args = constructor.parameterTypes.map { type: Class<*> -> val args = constructor.parameterTypes.map { type: Class<*> ->
if (type.isPrimitive) { if (type.isPrimitive) {
when (type.name) { return@map when (type.name) {
"boolean" -> return@map false "boolean" -> false
"byte" -> return@map 0.toByte() "byte" -> 0.toByte()
"char" -> return@map 0.toChar() "char" -> 0.toChar()
"short" -> return@map 0.toShort() "short" -> 0.toShort()
"int" -> return@map 0 "int" -> 0
"long" -> return@map 0L "long" -> 0L
"float" -> return@map 0f "float" -> 0f
"double" -> return@map 0.0 "double" -> 0.0
else -> null
} }
} }
null null

View File

@ -58,7 +58,7 @@ class HookAdapter(
return XposedBridge.invokeOriginalMethod(method(), thisObject(), args()) return XposedBridge.invokeOriginalMethod(method(), thisObject(), args())
} }
fun invokeOriginal(args: Array<Any>): Any? { fun invokeOriginal(args: Array<Any?>): Any? {
return XposedBridge.invokeOriginalMethod(method(), thisObject(), args) return XposedBridge.invokeOriginalMethod(method(), thisObject(), args)
} }

View File

@ -3,6 +3,7 @@ package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.common.data.MessageUpdate import me.rhunk.snapenhance.common.data.MessageUpdate
import me.rhunk.snapenhance.core.ModContext import me.rhunk.snapenhance.core.ModContext
import me.rhunk.snapenhance.core.util.CallbackBuilder import me.rhunk.snapenhance.core.util.CallbackBuilder
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
typealias CallbackResult = (error: String?) -> Unit typealias CallbackResult = (error: String?) -> Unit
@ -63,7 +64,7 @@ class ConversationManager(
fun displayedMessages(conversationId: String, messageId: Long, onResult: CallbackResult = {}) { fun displayedMessages(conversationId: String, messageId: Long, onResult: CallbackResult = {}) {
displayedMessagesMethod.invoke( displayedMessagesMethod.invoke(
instanceNonNull(), instanceNonNull(),
conversationId.toSnapUUID(), conversationId.toSnapUUID().instanceNonNull(),
messageId, messageId,
CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback")) CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback"))
.override("onSuccess") { onResult(null) } .override("onSuccess") { onResult(null) }
@ -87,16 +88,17 @@ class ConversationManager(
} }
fun fetchMessageByServerId(conversationId: String, serverMessageId: String, onSuccess: (Message) -> Unit, onError: (error: String) -> Unit) { fun fetchMessageByServerId(conversationId: String, serverMessageId: String, onSuccess: (Message) -> Unit, onError: (error: String) -> Unit) {
val serverMessageIdentifier = context.classCache.serverMessageIdentifier val serverMessageIdentifier = CallbackBuilder.createEmptyObject(context.classCache.serverMessageIdentifier.constructors.first())?.apply {
.getConstructor(context.classCache.snapUUID, Long::class.javaPrimitiveType) setObjectField("mServerConversationId", conversationId.toSnapUUID().instanceNonNull())
.newInstance(conversationId.toSnapUUID().instanceNonNull(), serverMessageId.toLong()) setObjectField("mServerMessageId", serverMessageId.toLong())
}
fetchMessageByServerId.invoke( fetchMessageByServerId.invoke(
instanceNonNull(), instanceNonNull(),
serverMessageIdentifier, serverMessageIdentifier,
CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback")) CallbackBuilder(context.mappings.getMappedClass("callbacks", "FetchMessageCallback"))
.override("onFetchMessageComplete") { param -> .override("onFetchMessageComplete") { param ->
onSuccess(Message(param.arg(1))) onSuccess(Message(param.arg(0)))
} }
.override("onError") { .override("onError") {
onError(it.arg<Any>(0).toString()) onError(it.arg<Any>(0).toString())