feat(message_logger): deleted indicator

This commit is contained in:
rhunk 2023-09-23 00:49:24 +02:00
parent 302ea7e83b
commit fc838fed7a
5 changed files with 97 additions and 20 deletions

View File

@ -9,20 +9,7 @@ import me.rhunk.snapenhance.core.Logger
import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import me.rhunk.snapmapper.Mapper
import me.rhunk.snapmapper.impl.BCryptClassMapper
import me.rhunk.snapmapper.impl.CallbackMapper
import me.rhunk.snapmapper.impl.CompositeConfigurationProviderMapper
import me.rhunk.snapmapper.impl.DefaultMediaItemMapper
import me.rhunk.snapmapper.impl.EnumMapper
import me.rhunk.snapmapper.impl.FriendRelationshipChangerMapper
import me.rhunk.snapmapper.impl.FriendsFeedEventDispatcherMapper
import me.rhunk.snapmapper.impl.MediaQualityLevelProviderMapper
import me.rhunk.snapmapper.impl.OperaPageViewControllerMapper
import me.rhunk.snapmapper.impl.PlatformAnalyticsCreatorMapper
import me.rhunk.snapmapper.impl.PlusSubscriptionMapper
import me.rhunk.snapmapper.impl.ScCameraSettingsMapper
import me.rhunk.snapmapper.impl.ScoreUpdateMapper
import me.rhunk.snapmapper.impl.StoryBoostStateMapper
import me.rhunk.snapmapper.impl.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.system.measureTimeMillis
@ -44,6 +31,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
CompositeConfigurationProviderMapper::class,
ScoreUpdateMapper::class,
FriendRelationshipChangerMapper::class,
ViewBinderMapper::class,
)
}

View File

@ -1,6 +1,8 @@
package me.rhunk.snapenhance.features.impl.spying
import android.graphics.drawable.ColorDrawable
import android.os.DeadObjectException
import android.view.View
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import me.rhunk.snapenhance.data.ContentType
@ -10,6 +12,8 @@ import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.hook.HookStage
import me.rhunk.snapenhance.hook.Hooker
import me.rhunk.snapenhance.hook.hook
import me.rhunk.snapenhance.hook.hookConstructor
import java.util.concurrent.Executors
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
@ -23,6 +27,7 @@ private fun Any.longHashCode(): Long {
class MessageLogger : Feature("MessageLogger",
loadParams = FeatureLoadParams.INIT_SYNC or
FeatureLoadParams.ACTIVITY_CREATE_SYNC or
FeatureLoadParams.ACTIVITY_CREATE_ASYNC
) {
companion object {
@ -30,6 +35,8 @@ class MessageLogger : Feature("MessageLogger",
const val PREFETCH_FEED_COUNT = 20
}
private val isEnabled get() = context.config.messaging.messageLogger.get()
private val threadPool = Executors.newFixedThreadPool(10)
//two level of cache to avoid querying the database
@ -57,13 +64,16 @@ class MessageLogger : Feature("MessageLogger",
private fun computeMessageIdentifier(conversationId: String, orderKey: Long) = (orderKey.toString() + conversationId).longHashCode()
private fun getServerMessageIdentifier(conversationId: String, clientMessageId: Long): Long? {
val serverMessageId = context.database.getConversationMessageFromId(clientMessageId)?.serverMessageId?.toLong() ?: return null
val serverMessageId = context.database.getConversationMessageFromId(clientMessageId)?.serverMessageId?.toLong() ?: return run {
context.log.error("Failed to get server message id for $conversationId $clientMessageId")
null
}
return computeMessageIdentifier(conversationId, serverMessageId)
}
@OptIn(ExperimentalTime::class)
override fun asyncOnActivityCreate() {
if (!context.database.hasArroyo()) {
if (!isEnabled || !context.database.hasArroyo()) {
return
}
@ -125,20 +135,60 @@ class MessageLogger : Feature("MessageLogger",
}
}
//set the message state to PREPARING for visibility
/*//set the message state to PREPARING for visibility
with(message.messageContent.contentType) {
if (this != ContentType.SNAP && this != ContentType.EXTERNAL_MEDIA) {
message.messageState = MessageState.PREPARING
}
}
}*/
deletedMessageCache[serverIdentifier] = deletedMessageObject
}
override fun init() {
val messageLogger by context.config.messaging.messageLogger
Hooker.hookConstructor(context.classCache.message, HookStage.AFTER, { messageLogger }) { param ->
Hooker.hookConstructor(context.classCache.message, HookStage.AFTER, { isEnabled }) { param ->
processSnapMessage(param.thisObject())
}
}
override fun onActivityCreate() {
if (!isEnabled) return
val viewBinderMappings = context.mappings.getMappedMap("ViewBinder")
val cachedHooks = mutableListOf<String>()
fun cacheHook(clazz: Class<*>, block: Class<*>.() -> Unit) {
if (!cachedHooks.contains(clazz.name)) {
clazz.block()
cachedHooks.add(clazz.name)
}
}
findClass(viewBinderMappings["class"].toString()).hookConstructor(HookStage.AFTER) { methodParam ->
cacheHook(
methodParam.thisObject<Any>()::class.java
) {
hook(viewBinderMappings["bindMethod"].toString(), HookStage.BEFORE) bindViewMethod@{ param ->
val instance = param.thisObject<Any>()
val model1 = param.arg<Any>(0).toString().also {
if (!it.startsWith("ChatViewModel")) return@bindViewMethod
}
val messageId = model1.substringAfter("messageId=").substringBefore(",").split(":").let {
it[0] to it[2]
}
getServerMessageIdentifier(messageId.first, messageId.second.toLong())?.let { serverMessageId ->
if (!deletedMessageCache.contains(serverMessageId)) return@bindViewMethod
} ?: return@bindViewMethod
val view = instance::class.java.methods.first {
it.name == viewBinderMappings["getViewMethod"].toString()
}.invoke(instance) as? View ?: return@bindViewMethod
view.foreground = ColorDrawable(0x1E90313e) // red with alpha
}
}
}
}
}

View File

@ -5,6 +5,7 @@ import org.jf.dexlib2.iface.ClassDef
fun ClassDef.isEnum(): Boolean = accessFlags and AccessFlags.ENUM.value != 0
fun ClassDef.isAbstract(): Boolean = accessFlags and AccessFlags.ABSTRACT.value != 0
fun ClassDef.isInterface(): Boolean = accessFlags and AccessFlags.INTERFACE.value != 0
fun ClassDef.isFinal(): Boolean = accessFlags and AccessFlags.FINAL.value != 0
fun ClassDef.hasStaticConstructorString(string: String): Boolean = methods.any {

View File

@ -0,0 +1,37 @@
package me.rhunk.snapmapper.impl
import me.rhunk.snapmapper.AbstractClassMapper
import me.rhunk.snapmapper.MapperContext
import me.rhunk.snapmapper.ext.getClassName
import me.rhunk.snapmapper.ext.isAbstract
import me.rhunk.snapmapper.ext.isInterface
import java.lang.reflect.Modifier
class ViewBinderMapper : AbstractClassMapper() {
override fun run(context: MapperContext) {
for (clazz in context.classes) {
if (!clazz.isAbstract() || clazz.isInterface()) continue
val getViewMethod = clazz.methods.firstOrNull { it.returnType == "Landroid/view/View;" && it.parameterTypes.size == 0 } ?: continue
// update view
clazz.methods.filter {
Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 1 && it.parameterTypes[0] == "Landroid/view/View;" && it.returnType == "V"
}.also {
if (it.size != 1) return@also
}.firstOrNull() ?: continue
val bindMethod = clazz.methods.filter {
Modifier.isAbstract(it.accessFlags) && it.parameterTypes.size == 2 && it.parameterTypes[0] == it.parameterTypes[1] && it.returnType == "V"
}.also {
if (it.size != 1) return@also
}.firstOrNull() ?: continue
context.addMapping("ViewBinder",
"class" to clazz.getClassName(),
"bindMethod" to bindMethod.name,
"getViewMethod" to getViewMethod.name
)
}
}
}

View File

@ -25,6 +25,7 @@ class TestMappings {
CompositeConfigurationProviderMapper::class,
ScoreUpdateMapper::class,
FriendRelationshipChangerMapper::class,
ViewBinderMapper::class
)
val gson = GsonBuilder().setPrettyPrinting().create()