feat: Auto Open optimization

This commit is contained in:
DarkKnight2122
2026-03-28 00:22:36 +05:30
parent f8fdd1893f
commit 0d42aed0ff
8 changed files with 244 additions and 88 deletions

View File

@@ -0,0 +1,7 @@
package me.eternal.purrfectsnap.bridge;
interface AutoOpenInterface {
int getProcessedCount();
List<String> getQueueItems(); // returns JSON serialized SnapQueueItem list
void reset();
}

View File

@@ -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();
}

View File

@@ -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."
}

View File

@@ -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) {

View File

@@ -34,6 +34,8 @@ abstract class Feature(
open fun init() {}
open fun onBridgeAction(action: String, extras: Map<String, Any>?, callback: (Any?) -> Unit) {}
protected fun findClass(name: String): Class<*> {
return context.androidContext.classLoader.loadClass(name)

View File

@@ -36,4 +36,20 @@ abstract class MessagingRuleFeature(name: String, val ruleType: MessagingRuleTyp
}
return state
}
override fun onBridgeAction(action: String, extras: Map<String, Any>?, 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)
}
}

View File

@@ -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<String> {
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<SnapQueueItem>()
private var snapQueueSize = AtomicInteger(0)
private val openedSnaps = mutableListOf<Long>()
private val openedSnaps = ArrayDeque<Long>()
private val isPaused = AtomicBoolean(false)
private val queuedSnaps = mutableListOf<SnapQueueItem>()
private var totalProcessed = AtomicInteger(0)
val queuedSnaps = mutableListOf<SnapQueueItem>()
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<Long>()
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<String, Any>?, callback: (Any?) -> Unit) {
if (action == "get_auto_open_status") {
val status = mutableMapOf<String, Any>()
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)

View File

@@ -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()
}
}