commit b925e8f54664d9aa7f92d43693a886051e1960af
parent 607521ec7605f09d7650a8e5f6b969d1f926fa60
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sun, 17 Dec 2023 19:09:01 +0100

feat(core/message_logger): auto purge & message filter
- optimize bridge addMessage

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt | 5+++++
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/SettingsSection.kt | 2+-
Mcommon/src/main/aidl/me/rhunk/snapenhance/bridge/MessageLoggerInterface.aidl | 4++--
Mcommon/src/main/assets/lang/en_US.json | 30+++++++++++++++++++++++++++++-
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MessageLoggerWrapper.kt | 23+++++++++++++++--------
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt | 35++++++++++++++++++++++++++++++++++-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/spying/MessageLogger.kt | 14++++++++++----
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt | 4++--
8 files changed, 98 insertions(+), 19 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt b/app/src/main/kotlin/me/rhunk/snapenhance/RemoteSideContext.kt @@ -108,6 +108,11 @@ class RemoteSideContext( streaksReminder.init() scriptManager.init() messageLogger.init() + config.root.messaging.messageLogger.takeIf { + it.globalState == true + }?.getAutoPurgeTime()?.let { + messageLogger.purgeAll(it) + } }.onFailure { log.error("Failed to load RemoteSideContext", it) } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/SettingsSection.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/sections/home/SettingsSection.kt @@ -161,7 +161,7 @@ class SettingsSection( } Button(onClick = { runCatching { - context.messageLogger.clearAll() + context.messageLogger.purgeAll() storedMessagesCount = 0 storedStoriesCount = 0 }.onFailure { diff --git a/common/src/main/aidl/me/rhunk/snapenhance/bridge/MessageLoggerInterface.aidl b/common/src/main/aidl/me/rhunk/snapenhance/bridge/MessageLoggerInterface.aidl @@ -15,12 +15,12 @@ interface MessageLoggerInterface { /** * Add a message to the message logger database if it is not already there */ - boolean addMessage(String conversationId, long id, in byte[] message); + oneway void addMessage(String conversationId, long id, in byte[] message); /** * Delete a message from the message logger database */ - void deleteMessage(String conversationId, long id); + oneway void deleteMessage(String conversationId, long id); /** * Add a story to the message logger database if it is not already there diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -405,7 +405,21 @@ }, "message_logger": { "name": "Message Logger", - "description": "Prevents messages from being deleted" + "description": "Prevents messages from being deleted", + "properties": { + "keep_my_own_messages": { + "name": "Keep My Own Messages", + "description": "Prevents your own messages from being deleted" + }, + "auto_purge": { + "name": "Auto Purge", + "description": "Automatically deletes cached messages that are older than the specified amount of time" + }, + "message_filter": { + "name": "Message Filter", + "description": "Select which messages should get logged (empty for all messages)" + } + } }, "auto_save_messages_in_conversations": { "name": "Auto Save Messages", @@ -806,6 +820,20 @@ "edit_text_override": { "multi_line_chat_input": "Multi Line Chat Input", "bypass_text_input_limit": "Bypass Text Input Limit" + }, + "auto_purge": { + "never": "Never", + "1_hour": "1 Hour", + "3_hours": "3 Hours", + "6_hours": "6 Hours", + "12_hours": "12 Hours", + "1_day": "1 Day", + "3_days": "3 Days", + "1_week": "1 Week", + "2_weeks": "2 Weeks", + "1_month": "1 Month", + "3_months": "3 Months", + "6_months": "6 Months" } } }, diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MessageLoggerWrapper.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/bridge/wrapper/MessageLoggerWrapper.kt @@ -26,12 +26,14 @@ class MessageLoggerWrapper( SQLiteDatabaseHelper.createTablesFromSchema(openedDatabase, mapOf( "messages" to listOf( "id INTEGER PRIMARY KEY", + "added_timestamp BIGINT", "conversation_id VARCHAR", "message_id BIGINT", "message_data BLOB" ), "stories" to listOf( "id INTEGER PRIMARY KEY", + "added_timestamp BIGINT", "user_id VARCHAR", "posted_timestamp BIGINT", "created_timestamp BIGINT", @@ -83,29 +85,33 @@ class MessageLoggerWrapper( return message } - override fun addMessage(conversationId: String, messageId: Long, serializedMessage: ByteArray): Boolean { + override fun addMessage(conversationId: String, messageId: Long, serializedMessage: ByteArray) { val cursor = database.rawQuery("SELECT message_id FROM messages WHERE conversation_id = ? AND message_id = ?", arrayOf(conversationId, messageId.toString())) val state = cursor.moveToFirst() cursor.close() - if (state) { - return false - } + if (state) return runBlocking { withContext(coroutineScope.coroutineContext) { database.insert("messages", null, ContentValues().apply { + put("added_timestamp", System.currentTimeMillis()) put("conversation_id", conversationId) put("message_id", messageId) put("message_data", serializedMessage) }) } } - return true } - fun clearAll() { + fun purgeAll(maxAge: Long? = null) { coroutineScope.launch { - database.execSQL("DELETE FROM messages") - database.execSQL("DELETE FROM stories") + maxAge?.let { + val maxTime = System.currentTimeMillis() - it + database.execSQL("DELETE FROM messages WHERE added_timestamp < ?", arrayOf(maxTime.toString())) + database.execSQL("DELETE FROM stories WHERE added_timestamp < ?", arrayOf(maxTime.toString())) + } ?: run { + database.execSQL("DELETE FROM messages") + database.execSQL("DELETE FROM stories") + } } } @@ -141,6 +147,7 @@ class MessageLoggerWrapper( withContext(coroutineScope.coroutineContext) { database.insert("stories", null, ContentValues().apply { put("user_id", userId) + put("added_timestamp", System.currentTimeMillis()) put("url", url) put("posted_timestamp", postedAt) put("created_timestamp", createdAt) diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt @@ -15,6 +15,39 @@ class MessagingTweaks : ConfigContainer() { } } + inner class MessageLoggerConfig : ConfigContainer(hasGlobalState = true) { + val keepMyOwnMessages = boolean("keep_my_own_messages") + private val autoPurge = unique("auto_purge", "1_hour", "3_hours", "6_hours", "12_hours", "1_day", "3_days", "1_week", "2_weeks", "1_month", "3_months", "6_months") { + disabledKey = "features.options.auto_purge.never" + }.apply { set("3_days") } + + fun getAutoPurgeTime(): Long? { + return when (autoPurge.getNullable()) { + "1_hour" -> 3600000L + "3_hours" -> 10800000L + "6_hours" -> 21600000L + "12_hours" -> 43200000L + "1_day" -> 86400000L + "3_days" -> 259200000L + "1_week" -> 604800000L + "2_weeks" -> 1209600000L + "1_month" -> 2592000000L + "3_months" -> 7776000000L + "6_months" -> 15552000000L + else -> null + } + } + + val messageFilter = multiple("message_filter", "CHAT", + "SNAP", + "NOTE", + "EXTERNAL_MEDIA", + "STICKER" + ) { + customOptionTranslationPath = "content_type" + } + } + val bypassScreenshotDetection = boolean("bypass_screenshot_detection") { requireRestart() } val anonymousStoryViewing = boolean("anonymous_story_viewing") val preventStoryRewatchIndicator = boolean("prevent_story_rewatch_indicator") { requireRestart() } @@ -42,7 +75,7 @@ class MessagingTweaks : ConfigContainer() { val notificationBlacklist = multiple("notification_blacklist", *NotificationType.getIncomingValues().map { it.key }.toTypedArray()) { customOptionTranslationPath = "features.options.notifications" } - val messageLogger = boolean("message_logger") { addNotices(FeatureNotice.UNSTABLE); requireRestart() } + val messageLogger = container("message_logger", MessageLoggerConfig()) { addNotices(FeatureNotice.UNSTABLE); requireRestart() } val galleryMediaSendOverride = boolean("gallery_media_send_override") { nativeHooks() } val stripMediaMetadata = multiple("strip_media_metadata", "hide_caption_text", "hide_snap_filters", "hide_extras", "remove_audio_note_duration", "remove_audio_note_transcript_capability") { requireRestart() } val bypassMessageRetentionPolicy = boolean("bypass_message_retention_policy") { addNotices(FeatureNotice.UNSTABLE); requireRestart() } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/spying/MessageLogger.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/spying/MessageLogger.kt @@ -34,7 +34,7 @@ class MessageLogger : Feature("MessageLogger", private val messageLoggerInterface by lazy { context.bridgeClient.getMessageLogger() } - val isEnabled get() = context.config.messaging.messageLogger.get() + val isEnabled get() = context.config.messaging.messageLogger.globalState == true private val threadPool = Executors.newFixedThreadPool(10) @@ -97,18 +97,24 @@ class MessageLogger : Feature("MessageLogger", } override fun init() { - context.event.subscribe(BuildMessageEvent::class, { isEnabled }, priority = 1) { event -> + if (!isEnabled) return + val keepMyOwnMessages = context.config.messaging.messageLogger.keepMyOwnMessages.get() + val messageFilter by context.config.messaging.messageLogger.messageFilter + + context.event.subscribe(BuildMessageEvent::class, priority = 1) { event -> val messageInstance = event.message.instanceNonNull() if (event.message.messageState != MessageState.COMMITTED) return@subscribe cachedIdLinks[event.message.messageDescriptor!!.messageId!!] = event.message.orderKey!! val conversationId = event.message.messageDescriptor!!.conversationId.toString() //exclude messages sent by me - if (event.message.senderId.toString() == context.database.myUserId) return@subscribe + if (!keepMyOwnMessages && event.message.senderId.toString() == context.database.myUserId) return@subscribe val uniqueMessageIdentifier = computeMessageIdentifier(conversationId, event.message.orderKey!!) + val messageContentType = event.message.messageContent!!.contentType - if (event.message.messageContent!!.contentType != ContentType.STATUS) { + if (messageContentType != ContentType.STATUS) { + if (messageFilter.isNotEmpty() && !messageFilter.contains(messageContentType?.name)) return@subscribe if (fetchedMessages.contains(uniqueMessageIdentifier)) return@subscribe fetchedMessages.add(uniqueMessageIdentifier) diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/ui/menu/impl/ChatActionMenu.kt @@ -66,7 +66,7 @@ class ChatActionMenu : AbstractMenu() { override fun init() { runCatching { - if (!context.config.downloader.chatDownloadContextMenu.get() && !context.config.messaging.messageLogger.get() && !context.isDeveloper) return + if (!context.config.downloader.chatDownloadContextMenu.get() && context.config.messaging.messageLogger.globalState != true && !context.isDeveloper) return context.androidContext.classLoader.loadClass("com.snap.messaging.chat.features.actionmenu.ActionMenuChatItemContainer") .hook("onMeasure", HookStage.BEFORE) { param -> param.setArg(1, @@ -136,7 +136,7 @@ class ChatActionMenu : AbstractMenu() { } //delete logged message button - if (context.config.messaging.messageLogger.get()) { + if (context.config.messaging.messageLogger.globalState == true) { injectButton(Button(viewGroup.context).apply { text = this@ChatActionMenu.context.translation["chat_action_menu.delete_logged_message_button"] setOnClickListener {