refactor: wrappers

This commit is contained in:
rhunk
2023-11-02 16:26:08 +01:00
parent 3fc06550bf
commit 07daeaf994
25 changed files with 149 additions and 147 deletions

View File

@ -176,7 +176,7 @@ class ExportChatMessages : AbstractAction() {
} }
fetchedMessages.firstOrNull()?.let { fetchedMessages.firstOrNull()?.let {
lastMessageId = it.messageDescriptor.messageId lastMessageId = it.messageDescriptor!!.messageId!!
} }
setStatus("Exporting (${foundMessages.size} / ${foundMessages.firstOrNull()?.orderKey})") setStatus("Exporting (${foundMessages.size} / ${foundMessages.firstOrNull()?.orderKey})")
} }

View File

@ -29,7 +29,7 @@ class ScopeSync : Feature("Scope Sync", loadParams = FeatureLoadParams.INIT_SYNC
if (event.messageContent.contentType != ContentType.SNAP) return@subscribe if (event.messageContent.contentType != ContentType.SNAP) return@subscribe
event.addCallbackResult("onSuccess") { event.addCallbackResult("onSuccess") {
event.destinations.conversations.map { it.toString() }.forEach { conversationId -> event.destinations.conversations!!.map { it.toString() }.forEach { conversationId ->
updateJobs[conversationId]?.also { it.cancel() } updateJobs[conversationId]?.also { it.cancel() }
updateJobs[conversationId] = (context.coroutineScope.launch { updateJobs[conversationId] = (context.coroutineScope.launch {

View File

@ -77,7 +77,7 @@ object MessageDecoder {
fun decode(messageContent: MessageContent): List<DecodedAttachment> { fun decode(messageContent: MessageContent): List<DecodedAttachment> {
return decode( return decode(
ProtoReader(messageContent.content), ProtoReader(messageContent.content!!),
customMediaReferences = getEncodedMediaReferences(gson.toJsonTree(messageContent.instanceNonNull())) customMediaReferences = getEncodedMediaReferences(gson.toJsonTree(messageContent.instanceNonNull()))
) )
} }

View File

@ -12,10 +12,10 @@ class SnapToChatMedia : Feature("SnapToChatMedia", loadParams = FeatureLoadParam
if (!context.config.experimental.snapToChatMedia.get()) return if (!context.config.experimental.snapToChatMedia.get()) return
context.event.subscribe(BuildMessageEvent::class, priority = 100) { event -> context.event.subscribe(BuildMessageEvent::class, priority = 100) { event ->
if (event.message.messageContent.contentType != ContentType.SNAP) return@subscribe if (event.message.messageContent!!.contentType != ContentType.SNAP) return@subscribe
val snapMessageContent = ProtoReader(event.message.messageContent.content).followPath(11)?.getBuffer() ?: return@subscribe val snapMessageContent = ProtoReader(event.message.messageContent!!.content!!).followPath(11)?.getBuffer() ?: return@subscribe
event.message.messageContent.content = ProtoWriter().apply { event.message.messageContent!!.content = ProtoWriter().apply {
from(3) { from(3) {
addBuffer(3, snapMessageContent) addBuffer(3, snapMessageContent)
} }

View File

@ -40,7 +40,7 @@ class BypassVideoLengthRestriction :
}) })
context.event.subscribe(SendMessageWithContentEvent::class) { event -> context.event.subscribe(SendMessageWithContentEvent::class) { event ->
if (event.destinations.stories.isEmpty()) return@subscribe if (event.destinations.stories!!.isEmpty()) return@subscribe
fileObserver.startWatching() fileObserver.startWatching()
} }
} }

View File

@ -26,8 +26,8 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
} }
private fun saveMessage(conversationId: SnapUUID, message: Message) { private fun saveMessage(conversationId: SnapUUID, message: Message) {
val messageId = message.messageDescriptor.messageId val messageId = message.messageDescriptor!!.messageId!!
if (messageLogger.takeIf { it.isEnabled }?.isMessageDeleted(conversationId.toString(), message.messageDescriptor.messageId) == true) return if (messageLogger.takeIf { it.isEnabled }?.isMessageDeleted(conversationId.toString(), messageId) == true) return
if (message.messageState != MessageState.COMMITTED) return if (message.messageState != MessageState.COMMITTED) return
runCatching { runCatching {
@ -50,8 +50,8 @@ 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 (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()
return autoSaveFilter.any { it == contentType } return autoSaveFilter.any { it == contentType }
} }
@ -96,7 +96,7 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
{ autoSaveFilter.isNotEmpty() } { autoSaveFilter.isNotEmpty() }
) { param -> ) { param ->
val message = Message(param.arg(0)) val message = Message(param.arg(0))
val conversationId = message.messageDescriptor.conversationId val conversationId = message.messageDescriptor!!.conversationId!!
if (!canSaveInConversation(conversationId.toString())) return@hook if (!canSaveInConversation(conversationId.toString())) return@hook
if (!canSaveMessage(message)) return@hook if (!canSaveMessage(message)) return@hook

View File

@ -68,11 +68,11 @@ class InstantDelete : Feature("InstantDelete", loadParams = FeatureLoadParams.AC
if (chatActionMenuOptions["chat_action_menu_erase_quote"] == menuOptionText.text) { if (chatActionMenuOptions["chat_action_menu_erase_quote"] == menuOptionText.text) {
conversationManager.fetchMessage(conversationId, messageId.toLong(), onSuccess = { message -> conversationManager.fetchMessage(conversationId, messageId.toLong(), onSuccess = { message ->
val quotedMessage = message.messageContent.quotedMessage.takeIf { it.isPresent() }!! val quotedMessage = message.messageContent!!.quotedMessage!!.takeIf { it.isPresent() }!!
conversationManager.updateMessage( conversationManager.updateMessage(
conversationId, conversationId,
quotedMessage.content.messageId, quotedMessage.content!!.messageId!!,
MessageUpdate.ERASE, MessageUpdate.ERASE,
onResult = onCallbackResult onResult = onCallbackResult
) )

View File

@ -27,7 +27,6 @@ import me.rhunk.snapenhance.core.features.FeatureLoadParams
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
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.spying.StealthMode import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
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.hook import me.rhunk.snapenhance.core.util.hook.hook
import me.rhunk.snapenhance.core.util.ktx.setObjectField import me.rhunk.snapenhance.core.util.ktx.setObjectField
@ -264,14 +263,14 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
} }
} }
val contentType = snapMessage.messageContent.contentType ?: return@onEach val contentType = snapMessage.messageContent!!.contentType ?: return@onEach
val contentData = snapMessage.messageContent.content val contentData = snapMessage.messageContent!!.content!!
val formatUsername: (String) -> String = { "$senderUsername: $it" } val formatUsername: (String) -> String = { "$senderUsername: $it" }
val notificationCache = cachedMessages.let { it.computeIfAbsent(conversationId) { mutableListOf() } } val notificationCache = cachedMessages.let { it.computeIfAbsent(conversationId) { mutableListOf() } }
val appendNotifications: () -> Unit = { setNotificationText(notificationData.notification, conversationId)} val appendNotifications: () -> Unit = { setNotificationText(notificationData.notification, conversationId)}
setupNotificationActionButtons(contentType, conversationId, snapMessage.messageDescriptor.messageId, notificationData) setupNotificationActionButtons(contentType, conversationId, snapMessage.messageDescriptor!!.messageId!!, notificationData)
when (contentType) { when (contentType) {
ContentType.NOTE -> { ContentType.NOTE -> {
@ -286,14 +285,14 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
} }
ContentType.SNAP, ContentType.EXTERNAL_MEDIA -> { ContentType.SNAP, ContentType.EXTERNAL_MEDIA -> {
val mediaReferences = MessageDecoder.getMediaReferences( val mediaReferences = MessageDecoder.getMediaReferences(
messageContent = context.gson.toJsonTree(snapMessage.messageContent.instanceNonNull()) messageContent = context.gson.toJsonTree(snapMessage.messageContent!!.instanceNonNull())
) )
val mediaReferenceKeys = mediaReferences.map { reference -> val mediaReferenceKeys = mediaReferences.map { reference ->
reference.asJsonObject.getAsJsonArray("mContentObject").map { it.asByte }.toByteArray() reference.asJsonObject.getAsJsonArray("mContentObject").map { it.asByte }.toByteArray()
} }
MessageDecoder.decode(snapMessage.messageContent).firstOrNull()?.also { media -> MessageDecoder.decode(snapMessage.messageContent!!).firstOrNull()?.also { media ->
val mediaType = MediaReferenceType.valueOf(mediaReferences.first().asJsonObject["mMediaType"].asString) val mediaType = MediaReferenceType.valueOf(mediaReferences.first().asJsonObject["mMediaType"].asString)
runCatching { runCatching {

View File

@ -58,7 +58,7 @@ class SendOverride : Feature("Send Override", loadParams = FeatureLoadParams.INI
if (localMessageContent.contentType != ContentType.EXTERNAL_MEDIA) return@subscribe if (localMessageContent.contentType != ContentType.EXTERNAL_MEDIA) return@subscribe
//prevent story replies //prevent story replies
val messageProtoReader = ProtoReader(localMessageContent.content) val messageProtoReader = ProtoReader(localMessageContent.content!!)
if (messageProtoReader.contains(7)) return@subscribe if (messageProtoReader.contains(7)) return@subscribe
event.canceled = true event.canceled = true

View File

@ -15,13 +15,13 @@ class UnlimitedSnapViewTime :
context.event.subscribe(BuildMessageEvent::class, { state }, priority = 101) { event -> context.event.subscribe(BuildMessageEvent::class, { state }, priority = 101) { event ->
if (event.message.messageState != MessageState.COMMITTED) return@subscribe if (event.message.messageState != MessageState.COMMITTED) return@subscribe
if (event.message.messageContent.contentType != ContentType.SNAP) return@subscribe if (event.message.messageContent!!.contentType != ContentType.SNAP) return@subscribe
val messageContent = event.message.messageContent val messageContent = event.message.messageContent
val mediaAttributes = ProtoReader(messageContent.content).followPath(11, 5, 2) ?: return@subscribe val mediaAttributes = ProtoReader(messageContent!!.content!!).followPath(11, 5, 2) ?: return@subscribe
if (mediaAttributes.contains(6)) return@subscribe if (mediaAttributes.contains(6)) return@subscribe
messageContent.content = ProtoEditor(messageContent.content).apply { messageContent.content = ProtoEditor(messageContent.content!!).apply {
edit(11, 5, 2) { edit(11, 5, 2) {
remove(8) remove(8)
addBuffer(6, byteArrayOf()) addBuffer(6, byteArrayOf())

View File

@ -101,14 +101,14 @@ class MessageLogger : Feature("MessageLogger",
val messageInstance = event.message.instanceNonNull() val messageInstance = event.message.instanceNonNull()
if (event.message.messageState != MessageState.COMMITTED) return@subscribe if (event.message.messageState != MessageState.COMMITTED) return@subscribe
cachedIdLinks[event.message.messageDescriptor.messageId] = event.message.orderKey cachedIdLinks[event.message.messageDescriptor!!.messageId!!] = event.message.orderKey!!
val conversationId = event.message.messageDescriptor.conversationId.toString() val conversationId = event.message.messageDescriptor!!.conversationId.toString()
//exclude messages sent by me //exclude messages sent by me
if (event.message.senderId.toString() == context.database.myUserId) return@subscribe if (event.message.senderId.toString() == context.database.myUserId) return@subscribe
val uniqueMessageIdentifier = computeMessageIdentifier(conversationId, event.message.orderKey) val uniqueMessageIdentifier = computeMessageIdentifier(conversationId, event.message.orderKey!!)
if (event.message.messageContent.contentType != ContentType.STATUS) { if (event.message.messageContent!!.contentType != ContentType.STATUS) {
if (fetchedMessages.contains(uniqueMessageIdentifier)) return@subscribe if (fetchedMessages.contains(uniqueMessageIdentifier)) return@subscribe
fetchedMessages.add(uniqueMessageIdentifier) fetchedMessages.add(uniqueMessageIdentifier)

View File

@ -13,13 +13,13 @@ import me.rhunk.snapenhance.core.features.impl.messaging.Messaging
fun me.rhunk.snapenhance.core.wrapper.impl.Message.toBridge(): Message { fun me.rhunk.snapenhance.core.wrapper.impl.Message.toBridge(): Message {
return Message().also { output -> return Message().also { output ->
output.conversationId = this.messageDescriptor.conversationId.toString() output.conversationId = this.messageDescriptor!!.conversationId.toString()
output.senderId = this.senderId.toString() output.senderId = this.senderId.toString()
output.clientMessageId = this.messageDescriptor.messageId output.clientMessageId = this.messageDescriptor!!.messageId!!
output.serverMessageId = this.orderKey output.serverMessageId = this.orderKey!!
output.contentType = this.messageContent.contentType?.id ?: -1 output.contentType = this.messageContent?.contentType?.id ?: -1
output.content = this.messageContent.content output.content = this.messageContent?.content
output.mediaReferences = MessageDecoder.getEncodedMediaReferences(this.messageContent) output.mediaReferences = MessageDecoder.getEncodedMediaReferences(this.messageContent!!)
} }
} }

View File

@ -74,8 +74,8 @@ class MessageExporter(
} }
private fun serializeMessageContent(message: Message): String? { private fun serializeMessageContent(message: Message): String? {
return if (message.messageContent.contentType == ContentType.CHAT) { return if (message.messageContent!!.contentType == ContentType.CHAT) {
ProtoReader(message.messageContent.content).getString(2, 1) ?: "Failed to parse message" ProtoReader(message.messageContent!!.content!!).getString(2, 1) ?: "Failed to parse message"
} else null } else null
} }
@ -93,8 +93,8 @@ class MessageExporter(
val sender = conversationParticipants[message.senderId.toString()] val sender = conversationParticipants[message.senderId.toString()]
val senderUsername = sender?.usernameForSorting ?: message.senderId.toString() val senderUsername = sender?.usernameForSorting ?: message.senderId.toString()
val senderDisplayName = sender?.displayName ?: message.senderId.toString() val senderDisplayName = sender?.displayName ?: message.senderId.toString()
val messageContent = serializeMessageContent(message) ?: message.messageContent.contentType?.name val messageContent = serializeMessageContent(message) ?: message.messageContent!!.contentType?.name
val date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date(message.messageMetadata.createdAt)) val date = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH).format(Date(message.messageMetadata!!.createdAt!!))
writer.write("[$date] - $senderDisplayName (${senderUsername}): $messageContent\n") writer.write("[$date] - $senderDisplayName (${senderUsername}): $messageContent\n")
} }
writer.flush() writer.flush()
@ -110,17 +110,17 @@ class MessageExporter(
fun updateProgress(type: String) { fun updateProgress(type: String) {
val total = messages.filter { val total = messages.filter {
mediaToDownload?.contains(it.messageContent.contentType) ?: false mediaToDownload?.contains(it.messageContent!!.contentType) ?: false
}.size }.size
processCount++ processCount++
printLog("$type $processCount/$total") printLog("$type $processCount/$total")
} }
messages.filter { messages.filter {
mediaToDownload?.contains(it.messageContent.contentType) ?: false mediaToDownload?.contains(it.messageContent!!.contentType) ?: false
}.forEach { message -> }.forEach { message ->
threadPool.execute { threadPool.execute {
MessageDecoder.decode(message.messageContent).forEach decode@{ attachment -> MessageDecoder.decode(message.messageContent!!).forEach decode@{ attachment ->
val protoMediaReference = Base64.UrlSafe.decode(attachment.mediaUrlKey ?: return@decode) val protoMediaReference = Base64.UrlSafe.decode(attachment.mediaUrlKey ?: return@decode)
runCatching { runCatching {
@ -145,8 +145,8 @@ class MessageExporter(
updateProgress("downloaded") updateProgress("downloaded")
}.onFailure { }.onFailure {
printLog("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}") printLog("failed to download media for ${message.messageDescriptor!!.conversationId}_${message.orderKey}")
context.log.error("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}", it) context.log.error("failed to download media for ${message.messageDescriptor!!.conversationId}_${message.orderKey}", it)
} }
} }
} }
@ -270,7 +270,7 @@ class MessageExporter(
add(JsonObject().apply { add(JsonObject().apply {
addProperty("orderKey", message.orderKey) addProperty("orderKey", message.orderKey)
addProperty("senderId", participants.getOrDefault(message.senderId.toString(), -1)) addProperty("senderId", participants.getOrDefault(message.senderId.toString(), -1))
addProperty("type", message.messageContent.contentType.toString()) addProperty("type", message.messageContent!!.contentType.toString())
fun addUUIDList(name: String, list: List<SnapUUID>) { fun addUUIDList(name: String, list: List<SnapUUID>) {
add(name, JsonArray().apply { add(name, JsonArray().apply {
@ -278,12 +278,12 @@ class MessageExporter(
}) })
} }
addUUIDList("savedBy", message.messageMetadata.savedBy) addUUIDList("savedBy", message.messageMetadata!!.savedBy!!)
addUUIDList("seenBy", message.messageMetadata.seenBy) addUUIDList("seenBy", message.messageMetadata!!.seenBy!!)
addUUIDList("openedBy", message.messageMetadata.openedBy) addUUIDList("openedBy", message.messageMetadata!!.openedBy!!)
add("reactions", JsonObject().apply { add("reactions", JsonObject().apply {
message.messageMetadata.reactions.forEach { reaction -> message.messageMetadata!!.reactions!!.forEach { reaction ->
addProperty( addProperty(
participants.getOrDefault(reaction.userId.toString(), -1L).toString(), participants.getOrDefault(reaction.userId.toString(), -1L).toString(),
reaction.reactionId reaction.reactionId
@ -291,13 +291,13 @@ class MessageExporter(
} }
}) })
addProperty("createdTimestamp", message.messageMetadata.createdAt) addProperty("createdTimestamp", message.messageMetadata!!.createdAt)
addProperty("readTimestamp", message.messageMetadata.readAt) addProperty("readTimestamp", message.messageMetadata!!.readAt)
addProperty("serializedContent", serializeMessageContent(message)) addProperty("serializedContent", serializeMessageContent(message))
addProperty("rawContent", Base64.UrlSafe.encode(message.messageContent.content)) addProperty("rawContent", Base64.UrlSafe.encode(message.messageContent!!.content!!))
add("attachments", JsonArray().apply { add("attachments", JsonArray().apply {
MessageDecoder.decode(message.messageContent) MessageDecoder.decode(message.messageContent!!)
.forEach attachments@{ attachments -> .forEach attachments@{ attachments ->
if (attachments.type == AttachmentType.STICKER) //TODO: implement stickers if (attachments.type == AttachmentType.STICKER) //TODO: implement stickers
return@attachments return@attachments

View File

@ -94,12 +94,12 @@ class MessageSender(
val localMessageContent = context.gson.fromJson(localMessageContentTemplate, context.classCache.localMessageContent) val localMessageContent = context.gson.fromJson(localMessageContentTemplate, context.classCache.localMessageContent)
val messageDestinations = MessageDestinations(AbstractWrapper.newEmptyInstance(context.classCache.messageDestinations)).also { val messageDestinations = MessageDestinations(AbstractWrapper.newEmptyInstance(context.classCache.messageDestinations)).also {
it.conversations = conversations it.conversations = conversations.toCollection(ArrayList())
it.mPhoneNumbers = arrayListOf() it.mPhoneNumbers = arrayListOf<Any>()
it.stories = arrayListOf() it.stories = arrayListOf<Any>()
} }
sendMessageWithContentMethod.invoke(context.feature(Messaging::class).conversationManager, messageDestinations.instanceNonNull(), localMessageContent, callback) sendMessageWithContentMethod.invoke(context.feature(Messaging::class).conversationManager?.instanceNonNull(), messageDestinations.instanceNonNull(), localMessageContent, callback)
} }
fun sendChatMessage(conversations: List<SnapUUID>, message: String, onError: (Any) -> Unit = {}, onSuccess: () -> Unit = {}) { fun sendChatMessage(conversations: List<SnapUUID>, message: String, onError: (Any) -> Unit = {}, onSuccess: () -> Unit = {}) {

View File

@ -2,24 +2,47 @@ package me.rhunk.snapenhance.core.wrapper
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import me.rhunk.snapenhance.core.util.CallbackBuilder import me.rhunk.snapenhance.core.util.CallbackBuilder
import me.rhunk.snapenhance.core.wrapper.impl.SnapUUID
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
abstract class AbstractWrapper( abstract class AbstractWrapper(
protected var instance: Any? protected var instance: Any?
) { ) {
protected val uuidArrayListMapper: (Any?) -> ArrayList<SnapUUID> get() = { (it as ArrayList<*>).map { i -> SnapUUID(i) }.toCollection(ArrayList()) }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
inner class EnumAccessor<T>(private val fieldName: String, private val defaultValue: T) { inner class EnumAccessor<T>(private val fieldName: String, private val defaultValue: T) {
operator fun getValue(obj: Any, property: KProperty<*>): T? = getEnumValue(fieldName, defaultValue as Enum<*>) as? T operator fun getValue(obj: Any, property: KProperty<*>): T? = getEnumValue(fieldName, defaultValue as Enum<*>) as? T
operator fun setValue(obj: Any, property: KProperty<*>, value: Any?) = setEnumValue(fieldName, value as Enum<*>) operator fun setValue(obj: Any, property: KProperty<*>, value: Any?) = setEnumValue(fieldName, value as Enum<*>)
} }
inner class FieldAccessor<T>(private val fieldName: String, private val mapper: ((Any?) -> T?)? = null) {
@Suppress("UNCHECKED_CAST")
operator fun getValue(obj: Any, property: KProperty<*>): T? {
val value = XposedHelpers.getObjectField(instance, fieldName)
return if (mapper != null) {
mapper.invoke(value)
} else {
value as? T
}
}
operator fun setValue(obj: Any, property: KProperty<*>, value: Any?) {
XposedHelpers.setObjectField(instance, fieldName, when (value) {
is AbstractWrapper -> value.instance
is ArrayList<*> -> value.map { if (it is AbstractWrapper) it.instance else it }.toMutableList()
else -> value
})
}
}
companion object { companion object {
fun newEmptyInstance(clazz: Class<*>): Any { fun newEmptyInstance(clazz: Class<*>): Any {
return CallbackBuilder.createEmptyObject(clazz.constructors[0]) ?: throw NullPointerException() return CallbackBuilder.createEmptyObject(clazz.constructors[0]) ?: throw NullPointerException()
} }
} }
fun instanceNonNull(): Any = instance!! fun instanceNonNull(): Any = instance ?: throw NullPointerException("Instance of ${this::class.simpleName} is null")
fun isPresent(): Boolean = instance != null fun isPresent(): Boolean = instance != null
override fun hashCode(): Int { override fun hashCode(): Int {
@ -31,6 +54,7 @@ abstract class AbstractWrapper(
} }
protected fun <T> enum(fieldName: String, defaultValue: T) = EnumAccessor(fieldName, defaultValue) protected fun <T> enum(fieldName: String, defaultValue: T) = EnumAccessor(fieldName, defaultValue)
protected fun <T> field(fieldName: String, mapper: ((Any?) -> T?)? = null) = FieldAccessor(fieldName, mapper)
fun <T : Enum<*>> getEnumValue(fieldName: String, defaultValue: T?): T? { fun <T : Enum<*>> getEnumValue(fieldName: String, defaultValue: T?): T? {
if (defaultValue == null) return null if (defaultValue == null) return null

View File

@ -7,7 +7,10 @@ import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
typealias CallbackResult = (error: String?) -> Unit typealias CallbackResult = (error: String?) -> Unit
class ConversationManager(val context: ModContext, obj: Any) : AbstractWrapper(obj) { 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 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 updateMessageMethod by lazy { findMethodByName("updateMessage") }

View File

@ -1,38 +0,0 @@
package me.rhunk.snapenhance.core.wrapper.impl
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.View
import me.rhunk.snapenhance.core.SnapEnhance
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
class FriendActionButton(
obj: View
) : AbstractWrapper(obj) {
private val iconDrawableContainer by lazy {
instanceNonNull().javaClass.declaredFields.first { it.type != Int::class.javaPrimitiveType }[instanceNonNull()]
}
private val setIconDrawableMethod by lazy {
iconDrawableContainer.javaClass.declaredMethods.first {
it.parameterTypes.size == 1 &&
it.parameterTypes[0] == Drawable::class.java &&
it.name != "invalidateDrawable" &&
it.returnType == Void::class.javaPrimitiveType
}
}
fun setIconDrawable(drawable: Drawable) {
setIconDrawableMethod.invoke(iconDrawableContainer, drawable)
}
companion object {
fun new(context: Context): FriendActionButton {
val instance = SnapEnhance.classLoader.loadClass("com.snap.profile.shared.view.FriendActionButton")
.getConstructor(Context::class.java, AttributeSet::class.java)
.newInstance(context, null) as View
return FriendActionButton(instance)
}
}
}

View File

@ -1,14 +1,21 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.common.data.MessageState import me.rhunk.snapenhance.common.data.MessageState
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
import org.mozilla.javascript.annotations.JSGetter
import org.mozilla.javascript.annotations.JSSetter
class Message(obj: Any?) : AbstractWrapper(obj) { class Message(obj: Any?) : AbstractWrapper(obj) {
val orderKey get() = instanceNonNull().getObjectField("mOrderKey") as Long @get:JSGetter @set:JSSetter
val senderId get() = SnapUUID(instanceNonNull().getObjectField("mSenderId")) var orderKey by field<Long>("mOrderKey")
val messageContent get() = MessageContent(instanceNonNull().getObjectField("mMessageContent")) @get:JSGetter @set:JSSetter
val messageDescriptor get() = MessageDescriptor(instanceNonNull().getObjectField("mDescriptor")) var senderId by field("mSenderId") { SnapUUID(it) }
val messageMetadata get() = MessageMetadata(instanceNonNull().getObjectField("mMetadata")) @get:JSGetter @set:JSSetter
var messageContent by field("mMessageContent") { MessageContent(it) }
@get:JSGetter @set:JSSetter
var messageDescriptor by field("mDescriptor") { MessageDescriptor(it) }
@get:JSGetter @set:JSSetter
var messageMetadata by field("mMetadata") { MessageMetadata(it) }
@get:JSGetter @set:JSSetter
var messageState by enum("mState", MessageState.COMMITTED) var messageState by enum("mState", MessageState.COMMITTED)
} }

View File

@ -1,15 +1,15 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.common.data.ContentType import me.rhunk.snapenhance.common.data.ContentType
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
import org.mozilla.javascript.annotations.JSGetter
import org.mozilla.javascript.annotations.JSSetter
class MessageContent(obj: Any?) : AbstractWrapper(obj) { class MessageContent(obj: Any?) : AbstractWrapper(obj) {
var content @get:JSGetter @set:JSSetter
get() = instanceNonNull().getObjectField("mContent") as ByteArray var content by field<ByteArray>("mContent")
set(value) = instanceNonNull().setObjectField("mContent", value) @get:JSGetter @set:JSSetter
val quotedMessage var quotedMessage by field("mQuotedMessage") { QuotedMessage(it) }
get() = QuotedMessage(instanceNonNull().getObjectField("mQuotedMessage")) @get:JSGetter @set:JSSetter
var contentType by enum("mContentType", ContentType.UNKNOWN) var contentType by enum("mContentType", ContentType.UNKNOWN)
} }

View File

@ -1,9 +1,11 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
import org.mozilla.javascript.annotations.JSGetter
import org.mozilla.javascript.annotations.JSSetter
class MessageDescriptor(obj: Any?) : AbstractWrapper(obj) { class MessageDescriptor(obj: Any?) : AbstractWrapper(obj) {
val messageId: Long get() = instanceNonNull().getObjectField("mMessageId") as Long @get:JSGetter @set:JSSetter
val conversationId: SnapUUID get() = SnapUUID(instanceNonNull().getObjectField("mConversationId")!!) var messageId by field<Long>("mMessageId")
val conversationId by field("mConversationId") { SnapUUID(it) }
} }

View File

@ -1,15 +1,10 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class MessageDestinations(obj: Any) : AbstractWrapper(obj){ class MessageDestinations(obj: Any) : AbstractWrapper(obj){
var conversations get() = (instanceNonNull().getObjectField("mConversations") as ArrayList<*>).map { SnapUUID(it) } var conversations by field("mConversations", uuidArrayListMapper)
set(value) = instanceNonNull().setObjectField("mConversations", value.map { it.instanceNonNull() }.toCollection(ArrayList())) var stories by field<ArrayList<*>>("mStories")
var stories get() = instanceNonNull().getObjectField("mStories") as ArrayList<Any> var mPhoneNumbers by field<ArrayList<*>>("mPhoneNumbers")
set(value) = instanceNonNull().setObjectField("mStories", value)
var mPhoneNumbers get() = instanceNonNull().getObjectField("mPhoneNumbers") as ArrayList<Any>
set(value) = instanceNonNull().setObjectField("mPhoneNumbers", value)
} }

View File

@ -1,28 +1,26 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.common.data.PlayableSnapState import me.rhunk.snapenhance.common.data.PlayableSnapState
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
import org.mozilla.javascript.annotations.JSGetter
import org.mozilla.javascript.annotations.JSSetter
class MessageMetadata(obj: Any?) : AbstractWrapper(obj){ class MessageMetadata(obj: Any?) : AbstractWrapper(obj){
val createdAt: Long get() = instanceNonNull().getObjectField("mCreatedAt") as Long @get:JSGetter @set:JSSetter
val readAt: Long get() = instanceNonNull().getObjectField("mReadAt") as Long var createdAt by field<Long>("mCreatedAt")
@get:JSGetter @set:JSSetter
var readAt by field<Long>("mReadAt")
@get:JSGetter @set:JSSetter
var playableSnapState by enum("mPlayableSnapState", PlayableSnapState.PLAYABLE) var playableSnapState by enum("mPlayableSnapState", PlayableSnapState.PLAYABLE)
private fun getUUIDList(name: String): List<SnapUUID> { @get:JSGetter @set:JSSetter
return (instanceNonNull().getObjectField(name) as List<*>).map { SnapUUID(it!!) } var savedBy by field("mSavedBy", uuidArrayListMapper)
} @get:JSGetter @set:JSSetter
var openedBy by field("mOpenedBy", uuidArrayListMapper)
val savedBy: List<SnapUUID> by lazy { @get:JSGetter @set:JSSetter
getUUIDList("mSavedBy") var seenBy by field("mSeenBy", uuidArrayListMapper)
} @get:JSGetter @set:JSSetter
val openedBy: List<SnapUUID> by lazy { var reactions by field("mReactions") {
getUUIDList("mOpenedBy") (it as ArrayList<*>).map { i -> UserIdToReaction(i) }.toMutableList()
}
val seenBy: List<SnapUUID> by lazy {
getUUIDList("mSeenBy")
}
val reactions: List<UserIdToReaction> by lazy {
(instanceNonNull().getObjectField("mReactions") as List<*>).map { UserIdToReaction(it!!) }
} }
} }

View File

@ -1,8 +1,10 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
import org.mozilla.javascript.annotations.JSGetter
import org.mozilla.javascript.annotations.JSSetter
class QuotedMessage(obj: Any?) : AbstractWrapper(obj) { class QuotedMessage(obj: Any?) : AbstractWrapper(obj) {
val content get() = QuotedMessageContent(instanceNonNull().getObjectField("mContent")) @get:JSGetter @set:JSSetter
var content by field("mContent") { QuotedMessageContent(it) }
} }

View File

@ -1,10 +1,10 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
import org.mozilla.javascript.annotations.JSGetter
import org.mozilla.javascript.annotations.JSSetter
class QuotedMessageContent(obj: Any?) : AbstractWrapper(obj) { class QuotedMessageContent(obj: Any?) : AbstractWrapper(obj) {
var messageId get() = instanceNonNull().getObjectField("mMessageId") as Long @get:JSGetter @set:JSSetter
set(value) = instanceNonNull().setObjectField("mMessageId", value) var messageId by field<Long>("mMessageId")
} }

View File

@ -1,11 +1,21 @@
package me.rhunk.snapenhance.core.wrapper.impl package me.rhunk.snapenhance.core.wrapper.impl
import me.rhunk.snapenhance.core.util.ktx.getObjectField import me.rhunk.snapenhance.core.util.ktx.getObjectField
import me.rhunk.snapenhance.core.util.ktx.setObjectField
import me.rhunk.snapenhance.core.wrapper.AbstractWrapper import me.rhunk.snapenhance.core.wrapper.AbstractWrapper
import org.mozilla.javascript.annotations.JSGetter
import org.mozilla.javascript.annotations.JSSetter
class UserIdToReaction(obj: Any?) : AbstractWrapper(obj) { class UserIdToReaction(obj: Any?) : AbstractWrapper(obj) {
val userId = SnapUUID(instanceNonNull().getObjectField("mUserId")) @get:JSGetter @set:JSSetter
val reactionId = (instanceNonNull().getObjectField("mReaction") var userId by field("mUserId") { SnapUUID(it) }
@get:JSGetter @set:JSSetter
var reactionId get() = (instanceNonNull().getObjectField("mReaction")
?.getObjectField("mReactionContent") ?.getObjectField("mReactionContent")
?.getObjectField("mIntentionType") as Long?) ?: 0 ?.getObjectField("mIntentionType") as Long?) ?: -1
set(value) {
instanceNonNull().getObjectField("mReaction")
?.getObjectField("mReactionContent")
?.setObjectField("mIntentionType", value)
}
} }