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:
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