From 0d42aed0ffac138dffe5b141fbf539a067855c9a Mon Sep 17 00:00:00 2001 From: DarkKnight2122 <145188583+DarkKnight2122@users.noreply.github.com> Date: Sat, 28 Mar 2026 00:22:36 +0530 Subject: [PATCH] feat: Auto Open optimization --- .../bridge/AutoOpenInterface.aidl | 7 + .../bridge/snapclient/MessagingBridge.aidl | 2 + common/src/main/assets/lang/en_US.json | 56 ++++- .../common/config/impl/MessagingTweaks.kt | 8 +- .../purrfectsnap/core/features/Feature.kt | 2 + .../core/features/MessagingRuleFeature.kt | 16 ++ .../impl/experiments/AutoOpenSnaps.kt | 237 ++++++++++++------ .../core/messaging/CoreMessagingBridge.kt | 4 + 8 files changed, 244 insertions(+), 88 deletions(-) create mode 100644 common/src/main/aidl/me/eternal/purrfectsnap/bridge/AutoOpenInterface.aidl diff --git a/common/src/main/aidl/me/eternal/purrfectsnap/bridge/AutoOpenInterface.aidl b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/AutoOpenInterface.aidl new file mode 100644 index 00000000..431dda34 --- /dev/null +++ b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/AutoOpenInterface.aidl @@ -0,0 +1,7 @@ +package me.eternal.purrfectsnap.bridge; + +interface AutoOpenInterface { + int getProcessedCount(); + List getQueueItems(); // returns JSON serialized SnapQueueItem list + void reset(); +} diff --git a/common/src/main/aidl/me/eternal/purrfectsnap/bridge/snapclient/MessagingBridge.aidl b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/snapclient/MessagingBridge.aidl index 6b7a4a58..cdb4461a 100644 --- a/common/src/main/aidl/me/eternal/purrfectsnap/bridge/snapclient/MessagingBridge.aidl +++ b/common/src/main/aidl/me/eternal/purrfectsnap/bridge/snapclient/MessagingBridge.aidl @@ -20,4 +20,6 @@ interface MessagingBridge { @nullable String updateMessage(String conversationId, long clientMessageId, String messageUpdate); @nullable String getOneToOneConversationId(String userId); + + me.eternal.purrfectsnap.bridge.AutoOpenInterface getAutoOpenInterface(); } \ No newline at end of file diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json index 0f68f8ae..3e6df82c 100644 --- a/common/src/main/assets/lang/en_US.json +++ b/common/src/main/assets/lang/en_US.json @@ -1,4 +1,4 @@ -{ +{ "setup": { "activity": { "wrong_apk_title": "Wrong APK installed", @@ -1611,6 +1611,13 @@ } }, "auto_open_snaps": { + "title": "Auto Open Snaps", + "status_monitoring": "Monitoring", + "status_active": "Active", + "status_paused": "Paused", + "processed_count": "Opened", + "queue_size": "Queue", + "action_reset": "Reset Statistics", "name": "Auto Open Snaps Settings", "description": "Configure delay and queue settings for Auto Open Snaps", "properties": { @@ -1637,7 +1644,18 @@ "retry_delay": { "name": "Retry Delay (ms)", "description": "Delay in milliseconds between retry attempts" - } + }, + "compact_notification": { + "name": "Auto Open Compact Notification", + "description": "Use a smaller, single-line notification for status updates" + }, + "only_on_wifi": { "name": "Auto Open only on Wi-Fi", "description": "Only process queue when connected to a Wi-Fi network to save mobile data" }, + "only_when_idle": { + "name": "Auto Open only when Idle", + "description": "Only process queue when the device is not in active use" + }, + "pause_during_gaming": { "name": "Pause Auto Open During Gaming", "description": "Automatically slow down processing when a resource intensive app or a game is in the foreground" }, + "safe_processing": { "name": "Auto Open with stealth pace", "description": "Adds variable delays and natural breaks to remain undetected. Turn off for maximum speed." } } }, "auto_delete_sent_messages": { @@ -1989,7 +2007,7 @@ "name": "HEVC Recording", "description": "Uses HEVC (H.265) codec for video recording" }, - "video_record_timer": { + "camera_tweaks": { "name": "Upgraded Camera Engine", "description": "Enables professional hardware ISP processing modes for better dynamic range" }, "audio_video": { "name": "Upgraded Audio and Video", "description": "Increases Video bitrate to 30Mbps and Audio to 320kbps/48kHz" }, "video_record_timer": { "name": "Video Recording Timer", "description": "Shows a recording timer overlay when recording video" } @@ -2127,7 +2145,7 @@ } } }, - "better_transcript": { + "network_optimization": { "name": "Network Optimization", "description": "Optimizes network socket buffers for higher throughput" }, "better_transcript": { "name": "Better Transcript", "description": "Improves the voice note transcript", "properties": { @@ -3199,9 +3217,7 @@ "export_failed_toast": "Failed to export account. Check logs for more info.", "forced_logout_toast": "Removed account due to forced logout" }, - "auto_open_snaps": { - "title": "Auto Open Snaps", - "priority_title": "Auto Open Snaps (Priority)", + "auto_open_snaps": { "title": "Auto Open Snaps", "processed_count": "Opened", "queue_size": "Queue", "action_reset": "Reset Statistics", "priority_title": "Auto Open Snaps (Priority)", "error_title": "Auto Open Snaps (Errors)", "channel_description": "Notifications for auto-opening snaps queue status", "priority_channel_description": "High priority notifications for auto-opening snaps", @@ -3768,4 +3784,30 @@ "openai": "OpenAI", "openrouter": "OpenRouter" } + , + "tasks_no_tasks": "No tasks", + "tasks_no_active_tasks": "No active tasks", + "tasks_no_scheduled_tasks": "No scheduled snaps", + "tasks_tab_active": "Active", + "tasks_tab_scheduled": "Scheduled", + "tasks_clear_button_description": "Clear tasks", + "tasks_delete_button": "Delete", + "tasks_merge_button": "Merge", + "tasks_summary_active": "{active} active · {recent} recent", + "tasks_summary_idle": "Idle · {recent} recent", + "tasks_running_count": "{count} running", + "tasks_tagline": "Monitor and manage background actions", + "tasks_failed_to_open_file": "Failed to open file", + "tasks_merge_files_toast": "Merging {count} files", + "tasks_remove_selected_tasks_title": "Are you sure you want to remove selected tasks?", + "tasks_remove_all_tasks_title": "Are you sure you want to remove all tasks?", + "tasks_remove_selected_tasks_confirm": "Remove {count} selected tasks?", + "tasks_remove_all_tasks_confirm": "This will stop all running tasks and clear the history." } + + + + + + + diff --git a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/MessagingTweaks.kt b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/MessagingTweaks.kt index 60f61183..9bbc72b6 100644 --- a/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/MessagingTweaks.kt +++ b/common/src/main/kotlin/me/eternal/purrfectsnap/common/config/impl/MessagingTweaks.kt @@ -175,7 +175,13 @@ class MessagingTweaks : ConfigContainer() { val retryDelay = integer("retry_delay", defaultValue = 3000) { inputCheck = { it.toIntOrNull()?.coerceAtLeast(1000) != null } } - + val compactNotification = boolean("compact_notification", false) + + // Resource Intelligence: Smart triggers for battery and data safety + val onlyOnWifi = boolean("only_on_wifi", false) + val onlyWhenIdle = boolean("only_when_idle", false) + val pauseDuringGaming = boolean("pause_during_gaming", false) + val safeProcessing = boolean("safe_processing", true) } class AutoDeleteSentMessagesConfig : ConfigContainer(hasGlobalState = true) { diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/Feature.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/Feature.kt index 7a7de781..544cced9 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/Feature.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/Feature.kt @@ -34,6 +34,8 @@ abstract class Feature( open fun init() {} + open fun onBridgeAction(action: String, extras: Map?, callback: (Any?) -> Unit) {} + protected fun findClass(name: String): Class<*> { return context.androidContext.classLoader.loadClass(name) diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/MessagingRuleFeature.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/MessagingRuleFeature.kt index 0b88a7d8..f9b41442 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/MessagingRuleFeature.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/MessagingRuleFeature.kt @@ -36,4 +36,20 @@ abstract class MessagingRuleFeature(name: String, val ruleType: MessagingRuleTyp } return state } + + override fun onBridgeAction(action: String, extras: Map?, callback: (Any?) -> Unit) { + if (action == "get_state") { + val conversationId = extras?.get("conversationId") as? String ?: return + callback(getState(conversationId)) + return + } + if (action == "set_state") { + val conversationId = extras?.get("conversationId") as? String ?: return + val state = extras["state"] as? Boolean ?: return + setState(conversationId, state) + callback(true) + return + } + super.onBridgeAction(action, extras, callback) + } } diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/AutoOpenSnaps.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/AutoOpenSnaps.kt index 23ff6bc3..8c368836 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/AutoOpenSnaps.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/features/impl/experiments/AutoOpenSnaps.kt @@ -9,6 +9,11 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.os.Build +import android.os.PowerManager +import android.app.ActivityManager +import android.net.ConnectivityManager +import android.net.NetworkCapabilities +import kotlinx.coroutines.isActive import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay @@ -26,16 +31,22 @@ import me.eternal.purrfectsnap.core.util.hook.hookConstructor import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicLong +import java.util.concurrent.ConcurrentHashMap import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlin.random.Random +import me.eternal.purrfectsnap.bridge.AutoOpenInterface +import com.google.gson.Gson + class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.AUTO_OPEN_SNAPS) { companion object { const val ACTION_PAUSE_RESUME = "me.eternal.purrfectsnap.AUTO_OPEN_SNAPS_PAUSE_RESUME" const val ACTION_CLEAR_QUEUE = "me.eternal.purrfectsnap.AUTO_OPEN_SNAPS_CLEAR_QUEUE" } + private val gson = Gson() + data class SnapQueueItem( val conversationId: String, val messageId: Long, @@ -45,18 +56,38 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A val timestamp: Long = System.currentTimeMillis() ) + private val autoOpenInterface = object : AutoOpenInterface.Stub() { + override fun getProcessedCount(): Int = totalProcessed + override fun getQueueItems(): List { + return synchronized(queuedSnaps) { + queuedSnaps.map { gson.toJson(it) } + } + } + override fun reset() { + synchronized(queuedSnaps) { + queuedSnaps.clear() + } + totalProcessed = 0 + updateStatusNotification() + } + } + + fun getInterface(): AutoOpenInterface = autoOpenInterface + private val snapQueue = MutableSharedFlow() - private var snapQueueSize = AtomicInteger(0) - private val openedSnaps = mutableListOf() + private val openedSnaps = ArrayDeque() private val isPaused = AtomicBoolean(false) - private val queuedSnaps = mutableListOf() - private var totalProcessed = AtomicInteger(0) + val queuedSnaps = mutableListOf() + var totalProcessed = 0 + private set - private var sessionStartTime = System.currentTimeMillis() + var sessionStartTime = System.currentTimeMillis() + private set private var lastResetTime = System.currentTimeMillis() - private var currentBatchSize = AtomicInteger(0) - private var currentBatchProcessed = AtomicInteger(0) + private var currentBatchSize = 0 + private var currentBatchProcessed = 0 + private var batchSnapCount = 0 // For jitter batch cooldown private val config by lazy { context.config.messaging.autoOpenSnaps } @@ -92,20 +123,20 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A val feedbackContent = if (wasPaused) { this@AutoOpenSnaps.context.translation["auto_open_snaps.resumed_message"] } else { - this@AutoOpenSnaps.context.translation["auto_open_snaps.paused_message"].replace("{count}", snapQueueSize.get().toString()) + this@AutoOpenSnaps.context.translation["auto_open_snaps.paused_message"].replace("{count}", synchronized(queuedSnaps) { queuedSnaps.size }.toString()) } showTemporaryNotification(feedbackTitle, feedbackContent) updateStatusNotification() - if (wasPaused && snapQueueSize.get() > 0) { - this@AutoOpenSnaps.context.log.debug("Resumed with ${snapQueueSize.get()} snaps in queue") + if (wasPaused && synchronized(queuedSnaps) { queuedSnaps.size } > 0) { + this@AutoOpenSnaps.context.log.debug("[AUTO-OPEN] Resumed with ${synchronized(queuedSnaps) { queuedSnaps.size }} snaps in queue") } } ACTION_CLEAR_QUEUE -> { - val queueSize = snapQueueSize.get() - val processedCount = totalProcessed.get() + val queueSize = synchronized(queuedSnaps) { queuedSnaps.size } + val processedCount = totalProcessed clearQueue(resetTotalCount = true, showNotification = false) @@ -129,26 +160,25 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A notificationManager.createNotificationChannel( NotificationChannel(baseChannelId, context.translation["auto_open_snaps.title"], - NotificationManager.IMPORTANCE_LOW).apply { + // Visible Presence Fix: Upgrade importance to DEFAULT so it stays in status bar. + NotificationManager.IMPORTANCE_DEFAULT).apply { description = context.translation["auto_open_snaps.channel_description"] - setShowBadge(false) + setShowBadge(true) setSound(null, null) enableVibration(false) } ) - + notificationManager.createNotificationChannel( NotificationChannel(priorityChannelId, context.translation["auto_open_snaps.priority_title"], - NotificationManager.IMPORTANCE_DEFAULT).apply { + NotificationManager.IMPORTANCE_HIGH).apply { description = context.translation["auto_open_snaps.priority_channel_description"] setShowBadge(true) setSound(null, null) enableVibration(false) } ) - - } private fun createPendingIntent(action: String): PendingIntent { @@ -165,12 +195,8 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A private fun verifyQueueSync(): Boolean { return synchronized(queuedSnaps) { - val actualSize = queuedSnaps.size - val atomicSize = snapQueueSize.get() - val currentTime = System.currentTimeMillis() val timeoutMs = 5 * 60 * 1000L - val originalSize = queuedSnaps.size val removed = mutableListOf() queuedSnaps.removeAll { item -> @@ -180,23 +206,14 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A } if (removed.isNotEmpty()) { - context.log.warn("Cleaned up ${removed.size} stuck items") + context.log.warn("[AUTO-OPEN] Cleaned up ${removed.size} stuck items") } - if (actualSize != atomicSize) { - context.log.warn("Queue size mismatch! Actual: $actualSize, Atomic: $atomicSize") - snapQueueSize.set(queuedSnaps.size) - - val uniqueItems = queuedSnaps.distinctBy { it.messageId }.toMutableList() - if (uniqueItems.size != queuedSnaps.size) { - context.log.warn("Found ${queuedSnaps.size - uniqueItems.size} duplicate items") - queuedSnaps.clear() - queuedSnaps.addAll(uniqueItems) - snapQueueSize.set(queuedSnaps.size) - } - return@synchronized false - } else { - snapQueueSize.set(queuedSnaps.size) + val uniqueItems = queuedSnaps.distinctBy { it.messageId }.toMutableList() + if (uniqueItems.size != queuedSnaps.size) { + context.log.warn("[AUTO-OPEN] Found ${queuedSnaps.size - uniqueItems.size} duplicate items") + queuedSnaps.clear() + queuedSnaps.addAll(uniqueItems) } return@synchronized true @@ -222,15 +239,25 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A lastNotificationUpdate.set(currentTime) updateStatusNotificationInternal() } - + private fun updateStatusNotificationInternal() { verifyQueueSync() - val queueCount = snapQueueSize.get() - val processed = totalProcessed.get() + val queueCount = synchronized(queuedSnaps) { queuedSnaps.size } + val processed = totalProcessed - if (queueCount <= 0 && processed <= 0) { - notificationManager.cancel(statusNotificationId) + // Self-Cleaning Logic: If work is done, wait 10s then auto-clear. + if (queueCount <= 0) { + if (processed > 0) { + context.coroutineScope.launch { + delay(10000) + if (synchronized(queuedSnaps) { queuedSnaps.size } <= 0) { + notificationManager.cancel(statusNotificationId) + } + } + } else { + notificationManager.cancel(statusNotificationId) + } return } @@ -258,10 +285,8 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A .setContentText(statusText) if (queueCount > 0) { - val batchSize = currentBatchSize.get() - val batchProcessed = currentBatchProcessed.get() - val progressMax = maxOf(batchSize, queueCount + batchProcessed) - val progressCurrent = batchProcessed + val progressMax = maxOf(currentBatchSize, queueCount + currentBatchProcessed) + val progressCurrent = currentBatchProcessed notificationBuilder.setProgress(progressMax, progressCurrent, false) @@ -299,7 +324,12 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A ) } - val recentSnaps = queuedSnaps.takeLast(5) + if (config.compactNotification.get()) { + notificationManager.notify(statusNotificationId, notificationBuilder.build()) + return + } + + val recentSnaps = synchronized(queuedSnaps) { queuedSnaps.takeLast(5) } val bigTextStyle = Notification.BigTextStyle() val detailText = buildString { @@ -352,7 +382,6 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A val clearedCount = synchronized(queuedSnaps) { val count = queuedSnaps.size queuedSnaps.clear() - snapQueueSize.set(0) count } @@ -361,12 +390,12 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A } if (resetTotalCount) { - totalProcessed.set(0) + totalProcessed = 0 lastResetTime = System.currentTimeMillis() } - currentBatchSize.set(0) - currentBatchProcessed.set(0) + currentBatchSize = 0 + currentBatchProcessed = 0 verifyQueueSync() @@ -376,7 +405,7 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A val message = if (resetTotalCount) { context.translation["auto_open_snaps.queue_cleared"] } else { - context.translation["auto_open_snaps.notification_queue_cleared_opened"].replace("{opened}", totalProcessed.get().toString()) + context.translation["auto_open_snaps.notification_queue_cleared_opened"].replace("{opened}", totalProcessed.toString()) } showTemporaryNotification(context.translation["auto_open_snaps.queue_cleared_title"], message) } @@ -488,7 +517,6 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A synchronized(queuedSnaps) { if (!queuedSnaps.any { it.messageId == snapItem.messageId }) { queuedSnaps.add(snapItem) - snapQueueSize.set(queuedSnaps.size) wasAddedToPausedQueue = true updateStatusNotification() } @@ -497,26 +525,63 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A delay(2000) } - val queueSizeAfterRemoval = synchronized(queuedSnaps) { + synchronized(queuedSnaps) { queuedSnaps.removeAll { it.messageId == snapItem.messageId } - snapQueueSize.set(queuedSnaps.size) - queuedSnaps.size } - val minDelayMs = config.minDelay.get().toLong() - val maxDelayMs = config.maxDelayMs.get().toLong() - val delayMs = if (maxDelayMs > minDelayMs) { - Random.nextLong(minDelayMs, maxDelayMs) - } else { - minDelayMs + // RESOURCE AWARENESS + val connectivityManager = context.androidContext.getSystemService(ConnectivityManager::class.java) + val isWifi = connectivityManager?.activeNetwork?.let { + connectivityManager.getNetworkCapabilities(it)?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + } == true + + val powerManager = context.androidContext.getSystemService(PowerManager::class.java) + val isIdle = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) powerManager?.isDeviceIdleMode == true else false + + val activityManager = context.androidContext.getSystemService(ActivityManager::class.java) + val isGaming = activityManager?.runningAppProcesses?.firstOrNull { + it.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + }?.processName?.let { name -> + !name.contains("snapchat") && !name.contains("purrfectsnap") + } ?: false + + // Immediate logging for visibility in [RESOURCE] filter + context.log.info("[RESOURCE] AutoOpen: Processing snap from ${snapItem.senderInfo}. Current state: WiFi=$isWifi, Idle=$isIdle, Gaming=$isGaming") + + while ( + (config.onlyOnWifi.get() && !isWifi) || + (config.onlyWhenIdle.get() && !isIdle) || + (config.pauseDuringGaming.get() && isGaming) + ) { + val waitTime = if (isGaming) 60000L else 5000L + context.log.warn("[RESOURCE] AutoOpen: Throttling queue due to resource constraints. Waiting ${waitTime}ms") + delay(waitTime) + if (!kotlin.coroutines.coroutineContext.isActive) return@collect } - delay(delayMs) + + // Stealth Pacing: Apply variable delays to remain undetected + if (config.safeProcessing.get()) { + batchSnapCount++ + val isRollingCooldown = batchSnapCount % 10 == 0 + val minDelayMs = if (isRollingCooldown) 5000L else config.minDelay.get().toLong() + val maxDelayMs = if (isRollingCooldown) 10000L else config.maxDelayMs.get().toLong() + + val jitter = if (maxDelayMs > minDelayMs) { + java.util.concurrent.ThreadLocalRandom.current().nextLong(minDelayMs, maxDelayMs) + } else minDelayMs + + context.log.verbose("[AUTO-OPEN] Stealth Pacing active. Waiting ${jitter}ms") + delay(jitter) + } else { + context.log.verbose("[AUTO-OPEN] Stealth Pacing disabled. Executing at maximum speed.") + } + var result: String? = null var lastError = "" for (i in 0 until config.retryAttempts.get()) { while ((!config.allowRunningInBackground.get() && context.isMainActivityPaused) || messaging.conversationManager == null) { - delay(2000) + delay(1000) } result = suspendCoroutine { continuation -> @@ -538,17 +603,16 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A } if (result == null || result == "DUPLICATEREQUEST") { - totalProcessed.incrementAndGet() - currentBatchProcessed.incrementAndGet() + totalProcessed++ + currentBatchProcessed++ + context.log.verbose("[AUTO-OPEN] Successfully opened ${snapItem.contentType} from ${snapItem.senderInfo}") } else { - context.log.error("Failed to open ${snapItem.contentType} from ${snapItem.senderInfo}: $lastError") + context.log.error("[AUTO-OPEN] Failed to open ${snapItem.contentType} from ${snapItem.senderInfo}: $lastError") } - val finalQueueSize = snapQueueSize.get() - - if (finalQueueSize <= 0) { - currentBatchSize.set(0) - currentBatchProcessed.set(0) + if (synchronized(queuedSnaps) { queuedSnaps.size } <= 0) { + currentBatchSize = 0 + currentBatchProcessed = 0 } updateStatusNotification() @@ -576,7 +640,8 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A if (openedSnaps.contains(clientMessageId)) { return@launch } - openedSnaps.add(clientMessageId) + if (openedSnaps.size >= 500) openedSnaps.removeFirst() + openedSnaps.addLast(clientMessageId) } val senderId = event.message.senderId?.toString() ?: context.translation["auto_open_snaps.unknown_sender"] @@ -592,7 +657,7 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A contentType = contentType ) - val actualQueueSize = synchronized(queuedSnaps) { + synchronized(queuedSnaps) { val existingItem = queuedSnaps.find { it.messageId == snapItem.messageId } if (existingItem != null) { return@launch @@ -603,12 +668,7 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A } queuedSnaps.add(snapItem) - snapQueueSize.set(queuedSnaps.size) - - val newSize = queuedSnaps.size - currentBatchSize.set(maxOf(currentBatchSize.get(), newSize)) - - queuedSnaps.size + currentBatchSize = maxOf(currentBatchSize, queuedSnaps.size) } updateStatusNotification() @@ -618,6 +678,23 @@ class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.A } } + override fun onBridgeAction(action: String, extras: Map?, callback: (Any?) -> Unit) { + if (action == "get_auto_open_status") { + val status = mutableMapOf() + status["processed"] = totalProcessed + status["queue"] = synchronized(queuedSnaps) { + queuedSnaps.map { item -> + mapOf( + "senderInfo" to item.senderInfo, + "contentType" to item.contentType, + "conversationType" to item.conversationType + ) + } + } + callback(status) + } + } + private fun getSenderDisplayName(senderId: String): String { return try { val friendInfo = context.database.getFriendInfo(senderId) diff --git a/core/src/main/kotlin/me/eternal/purrfectsnap/core/messaging/CoreMessagingBridge.kt b/core/src/main/kotlin/me/eternal/purrfectsnap/core/messaging/CoreMessagingBridge.kt index 09ad018a..85b91aa8 100644 --- a/core/src/main/kotlin/me/eternal/purrfectsnap/core/messaging/CoreMessagingBridge.kt +++ b/core/src/main/kotlin/me/eternal/purrfectsnap/core/messaging/CoreMessagingBridge.kt @@ -117,4 +117,8 @@ class CoreMessagingBridge( } override fun getOneToOneConversationId(userId: String) = context.database.getDMConversationId(userId) + + override fun getAutoOpenInterface(): me.eternal.purrfectsnap.bridge.AutoOpenInterface? { + return context.feature(me.eternal.purrfectsnap.core.features.impl.experiments.AutoOpenSnaps::class).getInterface() + } } \ No newline at end of file