pref: database and e2ee

- fix ff preview for e2e messages
This commit is contained in:
rhunk
2023-10-18 01:21:16 +02:00
parent ab846b11eb
commit 6f545905a1
6 changed files with 89 additions and 37 deletions

View File

@ -27,6 +27,7 @@ import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.logger.CoreLogger
import me.rhunk.snapenhance.core.manager.impl.ActionManager
import me.rhunk.snapenhance.core.manager.impl.FeatureManager
import me.rhunk.snapenhance.core.messaging.CoreMessagingBridge
import me.rhunk.snapenhance.core.messaging.MessageSender
import me.rhunk.snapenhance.core.scripting.CoreScriptRuntime
import me.rhunk.snapenhance.core.util.media.HttpServer
@ -61,6 +62,7 @@ class ModContext {
val eventDispatcher = EventDispatcher(this)
val native = NativeLib()
val scriptRuntime by lazy { CoreScriptRuntime(androidContext, log) }
val messagingBridge = CoreMessagingBridge(this)
val isDeveloper by lazy { config.scripting.developerMode.get() }

View File

@ -19,7 +19,6 @@ import me.rhunk.snapenhance.core.bridge.loadFromBridge
import me.rhunk.snapenhance.core.data.SnapClassCache
import me.rhunk.snapenhance.core.event.events.impl.SnapWidgetBroadcastReceiveEvent
import me.rhunk.snapenhance.core.event.events.impl.UnaryCallEvent
import me.rhunk.snapenhance.core.messaging.CoreMessagingBridge
import me.rhunk.snapenhance.core.util.LSPatchUpdater
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.hook
@ -117,7 +116,7 @@ class SnapEnhance {
logCritical(null, throwable)
}
}
bridgeClient.registerMessagingBridge(CoreMessagingBridge(this))
bridgeClient.registerMessagingBridge(messagingBridge)
reloadConfig()
actionManager.init()

View File

@ -26,6 +26,10 @@ class DatabaseAccess(private val context: ModContext) : Manager {
context.androidContext.getDatabasePath("main.db")
}
private val dmOtherParticipantCache by lazy {
getAllDMOtherParticipants().toMutableMap()
}
private fun openMain(): SQLiteDatabase {
return SQLiteDatabase.openDatabase(
mainDatabase.absolutePath,
@ -95,7 +99,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
val myUserId by lazy {
safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase ->
arroyoDatabase.rawQuery(buildString {
append("SELECT * FROM required_values WHERE key = 'USERID'")
append("SELECT value FROM required_values WHERE key = 'USERID'")
}, null).use { query ->
if (!query.moveToFirst()) {
return@safeDatabaseOperation null
@ -163,7 +167,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
fun getConversationType(conversationId: String): Int? {
return safeDatabaseOperation(openArroyo()) { database ->
database.rawQuery(
"SELECT * FROM user_conversation WHERE client_conversation_id = ?",
"SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId)
).use { query ->
if (!query.moveToFirst()) {
@ -186,10 +190,30 @@ class DatabaseAccess(private val context: ModContext) : Manager {
}
}
private fun getAllDMOtherParticipants(): Map<String, String?> {
return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase ->
arroyoDatabase.rawQuery(
"SELECT client_conversation_id, user_id FROM user_conversation WHERE conversation_type = 0",
null
).use { query ->
val participants = mutableMapOf<String, String>()
if (!query.moveToFirst()) {
return@safeDatabaseOperation null
}
do {
participants[query.getString(query.getColumnIndex("client_conversation_id"))] =
query.getString(query.getColumnIndex("user_id"))
} while (query.moveToNext())
participants
}
} ?: emptyMap()
}
fun getDMOtherParticipant(conversationId: String): String? {
if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId]
return safeDatabaseOperation(openArroyo()) { cursor ->
cursor.rawQuery(
"SELECT * FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0",
"SELECT user_id FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0",
arrayOf(conversationId)
).use { query ->
val participants = mutableListOf<String>()
@ -201,7 +225,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
} while (query.moveToNext())
participants.firstOrNull { it != myUserId }
}
}
}.also { dmOtherParticipantCache[conversationId] = it }
}
@ -214,7 +238,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
fun getConversationParticipants(conversationId: String): List<String>? {
return safeDatabaseOperation(openArroyo()) { arroyoDatabase: SQLiteDatabase ->
arroyoDatabase.rawQuery(
"SELECT * FROM user_conversation WHERE client_conversation_id = ?",
"SELECT user_id FROM user_conversation WHERE client_conversation_id = ?",
arrayOf(conversationId)
).use {
if (!it.moveToFirst()) {

View File

@ -12,6 +12,7 @@ import android.view.ViewGroup.MarginLayoutParams
import android.widget.Button
import android.widget.TextView
import me.rhunk.snapenhance.common.data.ContentType
import me.rhunk.snapenhance.common.data.MessageState
import me.rhunk.snapenhance.common.data.MessagingRuleType
import me.rhunk.snapenhance.common.data.RuleState
import me.rhunk.snapenhance.common.util.protobuf.ProtoEditor
@ -268,25 +269,26 @@ class EndToEndEncryption : MessagingRuleFeature(
}.digest()
}
private fun messageHook(conversationId: String, messageId: Long, senderId: String, messageContent: MessageContent) {
if (messageContent.contentType != ContentType.STATUS && decryptedMessageCache.containsKey(messageId)) {
val (contentType, buffer) = decryptedMessageCache[messageId]!!
messageContent.contentType = contentType
messageContent.content = buffer
return
fun tryDecryptMessage(senderId: String, clientMessageId: Long, conversationId: String, contentType: ContentType, messageBuffer: ByteArray): Pair<ContentType, ByteArray> {
if (contentType != ContentType.STATUS && decryptedMessageCache.containsKey(clientMessageId)) {
return decryptedMessageCache[clientMessageId]!!
}
val reader = ProtoReader(messageContent.content)
messageContent.contentType = fixContentType(messageContent.contentType!!, reader)
val reader = ProtoReader(messageBuffer)
var outputBuffer = messageBuffer
var outputContentType = fixContentType(contentType, reader) ?: contentType
val conversationParticipants by lazy {
getE2EParticipants(conversationId)
}
fun setMessageContent(buffer: ByteArray) {
messageContent.content = buffer
messageContent.contentType = fixContentType(messageContent.contentType, ProtoReader(buffer))
decryptedMessageCache[messageId] = messageContent.contentType!! to buffer
outputBuffer = buffer
outputContentType = fixContentType(outputContentType, ProtoReader(buffer)) ?: outputContentType
decryptedMessageCache[clientMessageId] = outputContentType to buffer
}
fun replaceMessageText(text: String) {
messageContent.content = ProtoWriter().apply {
outputBuffer = ProtoWriter().apply {
from(2) {
addString(1, text)
}
@ -297,9 +299,6 @@ class EndToEndEncryption : MessagingRuleFeature(
reader.followPath(2, 1) {
val messageTypeId = getVarInt(1)?.toInt() ?: return@followPath
val isMe = context.database.myUserId == senderId
val conversationParticipants by lazy {
getE2EParticipants(conversationId)
}
if (messageTypeId == ENCRYPTED_MESSAGE_ID) {
runCatching {
@ -314,7 +313,7 @@ class EndToEndEncryption : MessagingRuleFeature(
setMessageContent(
e2eeInterface.decryptMessage(participantId, ciphertext, iv)
)
encryptedMessages.add(messageId)
encryptedMessages.add(clientMessageId)
return@eachBuffer
}
@ -323,14 +322,14 @@ class EndToEndEncryption : MessagingRuleFeature(
setMessageContent(
e2eeInterface.decryptMessage(senderId, ciphertext, iv)
)
encryptedMessages.add(messageId)
encryptedMessages.add(clientMessageId)
}
}.onFailure {
context.log.error("Failed to decrypt message id: $messageId", it)
messageContent.contentType = ContentType.CHAT
messageContent.content = ProtoWriter().apply {
context.log.error("Failed to decrypt message id: $clientMessageId", it)
outputContentType = ContentType.CHAT
outputBuffer = ProtoWriter().apply {
from(2) {
addString(1, "Failed to decrypt message, id=$messageId. Check logcat for more details.")
addString(1, "Failed to decrypt message, id=$clientMessageId. Check logcat for more details.")
}
}.toByteArray()
}
@ -354,15 +353,23 @@ class EndToEndEncryption : MessagingRuleFeature(
when (messageTypeId) {
REQUEST_PK_MESSAGE_ID -> {
pkRequests[messageId] = payload
pkRequests[clientMessageId] = payload
replaceMessageText("You just received a public key request. Click below to accept it.")
}
RESPONSE_SK_MESSAGE_ID -> {
secretResponses[messageId] = payload
secretResponses[clientMessageId] = payload
replaceMessageText("Your friend just accepted your public key. Click below to accept the secret.")
}
}
}
return outputContentType to outputBuffer
}
private fun messageHook(conversationId: String, messageId: Long, senderId: String, messageContent: MessageContent) {
val (contentType, buffer) = tryDecryptMessage(senderId, messageId, conversationId, messageContent.contentType ?: ContentType.CHAT, messageContent.content)
messageContent.contentType = contentType
messageContent.content = buffer
}
override fun asyncInit() {
@ -474,6 +481,7 @@ class EndToEndEncryption : MessagingRuleFeature(
context.event.subscribe(BuildMessageEvent::class, priority = 0) { event ->
val message = event.message
if (message.messageState != MessageState.COMMITTED) return@subscribe
val conversationId = message.messageDescriptor.conversationId.toString()
messageHook(
conversationId = conversationId,

View File

@ -15,8 +15,10 @@ import me.rhunk.snapenhance.common.util.protobuf.ProtoReader
import me.rhunk.snapenhance.core.event.events.impl.BindViewEvent
import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.features.FeatureLoadParams
import me.rhunk.snapenhance.core.features.impl.experiments.EndToEndEncryption
import me.rhunk.snapenhance.core.ui.addForegroundDrawable
import me.rhunk.snapenhance.core.ui.removeForegroundDrawable
import me.rhunk.snapenhance.core.util.EvictingMap
import kotlin.math.absoluteValue
@SuppressLint("DiscouragedApi")
@ -27,12 +29,17 @@ class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview", loadParams
).getColor(0, 0)
}
private val friendNameCache = EvictingMap<String, String>(100)
private fun getDimens(name: String) = context.resources.getDimensionPixelSize(context.resources.getIdentifier(name, "dimen", Constants.SNAPCHAT_PACKAGE_NAME))
override fun onActivityCreate() {
val setting = context.config.userInterface.friendFeedMessagePreview
if (setting.globalState != true) return
val hasE2EE = context.config.experimental.e2eEncryption.globalState == true
val endToEndEncryption by lazy { context.feature(EndToEndEncryption::class) }
val ffItemId = context.resources.getIdentifier("ff_item", "id", Constants.SNAPCHAT_PACKAGE_NAME)
val secondaryTextSize = getDimens("ff_feed_cell_secondary_text_size").toFloat()
@ -58,17 +65,29 @@ class FriendFeedMessagePreview : Feature("FriendFeedMessagePreview", loadParams
frameLayout.removeForegroundDrawable("ffItem")
val stringMessages = context.database.getMessagesFromConversationId(conversationId, setting.amount.get().absoluteValue)?.mapNotNull { message ->
val messageContainer = message.messageContent
?.let { ProtoReader(it) }
?.followPath(4, 4)
val messageContainer =
message.messageContent
?.let { ProtoReader(it) }
?.followPath(4, 4)?.let { messageReader ->
takeIf { hasE2EE }?.let takeIf@{
endToEndEncryption.tryDecryptMessage(
senderId = message.senderId ?: return@takeIf null,
clientMessageId = message.clientMessageId.toLong(),
conversationId = message.clientConversationId ?: return@takeIf null,
contentType = ContentType.fromId(message.contentType),
messageBuffer = messageReader.getBuffer()
).second
}?.let { ProtoReader(it) } ?: messageReader
}
?: return@mapNotNull null
val messageString = messageContainer.getString(2, 1)
?: ContentType.fromMessageContainer(messageContainer)?.name
?: return@mapNotNull null
val friendName = context.database.getFriendInfo(message.senderId ?: return@mapNotNull null)?.let { it.displayName?: it.mutableUsername } ?: "Unknown"
val friendName = friendNameCache.getOrPut(message.senderId ?: return@mapNotNull null) {
context.database.getFriendInfo(message.senderId ?: return@mapNotNull null)?.let { it.displayName?: it.mutableUsername } ?: "Unknown"
}
"$friendName: $messageString"
}?.reversed() ?: return@friendFeedItem

View File

@ -36,7 +36,7 @@ class CoreMessagingBridge(
val callback = CallbackBuilder(
context.mappings.getMappedClass("callbacks", "FetchMessageCallback")
).override("onFetchMessageComplete") { param ->
val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(1)).toBridge()
val message = me.rhunk.snapenhance.core.wrapper.impl.Message(param.arg(0)).toBridge()
continuation.resumeWith(Result.success(message))
}
.override("onServerRequest", shouldUnhook = false) {}
@ -47,7 +47,7 @@ class CoreMessagingBridge(
context.classCache.conversationManager.methods.first { it.name == "fetchMessage" }.invoke(
conversationManager,
SnapUUID.fromString(conversationId).instanceNonNull(),
clientMessageId,
clientMessageId.toLong(),
callback
)
}