commit b1abac22ee38a9db35c893f8cc469e6cd7af7454
parent 68c9a4cc4e589daa9af4a08282f3f7c4eaaacb3e
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Mon, 21 Jul 2025 16:33:09 +0200

fix(core): arroyo conversation

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>

Diffstat:
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt | 4++--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt | 3+--
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt | 125+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt | 2+-
4 files changed, 98 insertions(+), 36 deletions(-)

diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/SnapEnhance.kt @@ -325,7 +325,7 @@ class SnapEnhance { MessagingFriendInfo( friendUserId, - appContext.database.getConversationLinkFromUserId(friendUserId)?.clientConversationId, + it.key, friend.displayName, friend.mutableUsername ?: friend.usernameForSorting!!, friend.bitmojiAvatarId, @@ -344,7 +344,7 @@ class SnapEnhance { return appContext.database.getFriendInfo(uuid)?.let { MessagingFriendInfo( userId = it.userId!!, - dmConversationId = appContext.database.getConversationLinkFromUserId(it.userId!!)?.clientConversationId, + dmConversationId = null, displayName = it.displayName, mutableUsername = it.mutableUsername!!, bitmojiId = it.bitmojiAvatarId, diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/action/impl/BulkMessagingAction.kt @@ -54,7 +54,6 @@ import me.rhunk.snapenhance.core.ui.ViewAppearanceHelper import me.rhunk.snapenhance.core.util.EvictingMap import me.rhunk.snapenhance.core.util.dataBuilder import me.rhunk.snapenhance.mapper.impl.FriendRelationshipChangerMapper -import java.net.URL import java.text.DateFormat import java.util.Date import kotlin.random.Random @@ -184,7 +183,7 @@ class BulkMessagingAction : AbstractAction() { } private fun getDMLastMessage(userId: String?): ConversationMessage? { - return context.database.getConversationLinkFromUserId(userId ?: return null)?.clientConversationId?.let { + return context.database.getDMConversationId(userId ?: return null)?.let { context.database.getMessagesFromConversationId(it, 1) }?.firstOrNull() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/database/DatabaseAccess.kt @@ -13,6 +13,7 @@ import me.rhunk.snapenhance.common.util.ktx.getInteger import me.rhunk.snapenhance.common.util.ktx.getStringOrNull import me.rhunk.snapenhance.common.util.protobuf.ProtoReader import me.rhunk.snapenhance.core.ModContext +import me.rhunk.snapenhance.core.wrapper.impl.toSnapUUID import me.rhunk.snapenhance.nativelib.NativeLib @@ -30,6 +31,14 @@ class DatabaseAccess( ) { private val openedDatabases = mutableMapOf<DatabaseType, SQLiteDatabase>() + private val hasArroyoConversationTable by lazy { + useDatabase(DatabaseType.ARROYO)?.performOperation { + safeRawQuery("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'conversation'")?.use { query -> + query.moveToFirst() && query.getStringOrNull("name") == "conversation" + } + } == true + } + private fun useDatabase(database: DatabaseType, writeMode: Boolean = false): SQLiteDatabase? { // only cache read-only databases if (!writeMode && openedDatabases.containsKey(database) && openedDatabases[database]?.isOpen == true) { @@ -88,7 +97,44 @@ class DatabaseAccess( }.getOrNull() } + private val friendDMsCache by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + getFeedEntries(Int.MAX_VALUE) + .filter { it.conversationType == 0 && it.participantsSize == 2 } + .associate { it.participants?.firstOrNull { it != myUserId } to it.key } + .toMutableMap() + } + private val dmOtherParticipantCache by lazy { + if (hasArroyoConversationTable) { + return@lazy useDatabase(DatabaseType.ARROYO)?.performOperation { + safeRawQuery( + "SELECT client_conversation_id, conversation_metadata FROM conversation", + )?.use { query -> + val result = mutableMapOf<String, String?>() + if (!query.moveToFirst()) { + return@performOperation null + } + do { + val conversationId = query.getStringOrNull("client_conversation_id") ?: continue + val conversationMetadata = ProtoReader(query.getBlobOrNull("conversation_metadata") ?: continue) + + val participants = mutableListOf<String>() + conversationMetadata.eachBuffer(3) { + participants.add(getByteArray(1, 1)?.toSnapUUID()?.toString() ?: return@eachBuffer) + } + + result[conversationId] = if (participants.size == 2) { + participants.firstOrNull { it != myUserId }?.also { + result[it] = null + } + } else null + } while (query.moveToNext()) + + result + } + }?.toMutableMap() ?: mutableMapOf() + } + (useDatabase(DatabaseType.ARROYO)?.performOperation { safeRawQuery( "SELECT client_conversation_id, conversation_type, user_id FROM user_conversation WHERE user_id != ?", @@ -293,6 +339,10 @@ class DatabaseAccess( } fun getConversationType(conversationId: String): Int? { + if (hasArroyoConversationTable) { + return getFeedEntryByConversationId(conversationId)?.conversationType + } + return useDatabase(DatabaseType.ARROYO)?.performOperation { safeRawQuery( "SELECT conversation_type FROM user_conversation WHERE client_conversation_id = ?", @@ -306,36 +356,67 @@ class DatabaseAccess( } } - fun getConversationLinkFromUserId(userId: String): UserConversationLink? { + fun getDMConversationId(userId: String): String? { + if (hasArroyoConversationTable) { + return friendDMsCache[userId] + } + return useDatabase(DatabaseType.ARROYO)?.performOperation { readDatabaseObject( UserConversationLink(), "user_conversation", "user_id = ? AND conversation_type = 0", arrayOf(userId) - ) + )?.clientConversationId } } - fun getDMOtherParticipant(conversationId: String): String? { - if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId] + private fun getConversationParticipantsRaw(conversationId: String): List<String>? { + if (hasArroyoConversationTable) { + return useDatabase(DatabaseType.ARROYO)?.performOperation { + safeRawQuery( + "SELECT conversation_metadata FROM conversation WHERE client_conversation_id = ?", + arrayOf(conversationId) + )?.use { query -> + val participants = mutableListOf<String>() + if (!query.moveToFirst()) { + return@performOperation null + } + val conversationMetadata = ProtoReader(query.getBlobOrNull("conversation_metadata") ?: return@performOperation null) + + conversationMetadata.eachBuffer(3) { + participants.add(getByteArray(1, 1)?.toSnapUUID()?.toString() ?: return@eachBuffer) + } + + participants + } + } + } + return useDatabase(DatabaseType.ARROYO)?.performOperation { safeRawQuery( - "SELECT user_id FROM user_conversation WHERE client_conversation_id = ? AND conversation_type = 0", + "SELECT user_id FROM user_conversation WHERE client_conversation_id = ?", arrayOf(conversationId) )?.use { query -> - val participants = mutableListOf<String>() if (!query.moveToFirst()) { - return@performOperation null + return@performOperation emptyList() } + val participants = mutableListOf<String>() do { - participants.add(query.getStringOrNull("user_id")!!) + query.getStringOrNull("user_id")?.let { participants.add(it) } } while (query.moveToNext()) - participants.firstOrNull { it != myUserId } - }.also { dmOtherParticipantCache[conversationId] = it } + participants + } } } + fun getDMOtherParticipant(conversationId: String): String? { + if (dmOtherParticipantCache.containsKey(conversationId)) return dmOtherParticipantCache[conversationId] + + return getConversationParticipantsRaw(conversationId)?.takeIf { it.size == 2 }?.firstOrNull { it != myUserId }.also { + dmOtherParticipantCache[conversationId] = it + } + } fun getStoryEntryFromId(storyId: String): StoryEntry? { return useDatabase(DatabaseType.MAIN)?.performOperation { @@ -345,28 +426,10 @@ class DatabaseAccess( fun getConversationParticipants(conversationId: String, useCache: Boolean = true): List<String>? { if (dmOtherParticipantCache[conversationId] != null && useCache) return dmOtherParticipantCache[conversationId]?.let { listOf(myUserId, it) } - return useDatabase(DatabaseType.ARROYO)?.performOperation { - safeRawQuery( - "SELECT user_id, conversation_type FROM user_conversation WHERE client_conversation_id = ?", - arrayOf(conversationId) - )?.use { cursor -> - if (!cursor.moveToFirst()) { - return@performOperation null - } - val participants = mutableListOf<String>() - var conversationType = -1 - do { - if (conversationType == -1) conversationType = cursor.getInteger("conversation_type") - participants.add(cursor.getStringOrNull("user_id")!!) - } while (cursor.moveToNext()) - if (!dmOtherParticipantCache.containsKey(conversationId)) { - dmOtherParticipantCache[conversationId] = when (conversationType) { - 0 -> participants.firstOrNull { it != myUserId } - else -> null - } - } - participants + return getConversationParticipantsRaw(conversationId)?.also { + if (!dmOtherParticipantCache.containsKey(conversationId)) { + dmOtherParticipantCache[conversationId] = it.firstOrNull { it != myUserId } } } } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/messaging/CoreMessagingBridge.kt @@ -116,5 +116,5 @@ class CoreMessagingBridge( } } - override fun getOneToOneConversationId(userId: String) = context.database.getConversationLinkFromUserId(userId)?.clientConversationId + override fun getOneToOneConversationId(userId: String) = context.database.getDMConversationId(userId) } \ No newline at end of file