commit 68c61a0b73f70f2c508d4dbc28eaf02ff3c46c46
parent 785fb49aa98f3e53ee11061d4667eef9bfdc1c8b
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed, 17 Apr 2024 23:10:22 +0200

feat(experimental): auto open snaps

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 14++++++++++++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt | 1+
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt | 1+
Acore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AutoOpenSnaps.kt | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 138 insertions(+), 0 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -199,6 +199,14 @@ "whitelist": "Unsaveable Messages" } }, + "auto_open_snaps": { + "name": "Auto Open Snaps", + "description": "Automatically opens Snaps when receiving them", + "options": { + "blacklist": "Exclude from Auto Open Snaps", + "whitelist": "Auto Open Snaps" + } + }, "hide_friend_feed": { "name": "Hide from Friend Feed" }, @@ -945,6 +953,7 @@ "auto_download": "\u2B07\uFE0F Auto Download", "auto_save": "\uD83D\uDCAC Auto Save Messages", "unsaveable_messages": "\u2B07\uFE0F Unsaveable Messages", + "auto_open_snaps": "\uD83D\uDCF7 Auto Open Snaps", "stealth": "\uD83D\uDC7B Stealth Mode", "mark_snaps_as_seen": "\uD83D\uDC40 Mark Snaps as seen", "mark_stories_as_seen_locally": "\uD83D\uDC40 Mark Stories as seen locally", @@ -1357,6 +1366,11 @@ "incoming_secret_message": "Your friend just accepted your public key. Click below to accept the secret." }, + "auto_open_snaps": { + "title": "Auto Open Snaps", + "notification_content": "{count} Snaps opened" + }, + "suspend_location_updates": { "switch_text": "Suspend Location Updates" }, diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/data/MessagingCoreObjects.kt @@ -45,6 +45,7 @@ enum class MessagingRuleType( STEALTH("stealth", true), AUTO_DOWNLOAD("auto_download", true), AUTO_SAVE("auto_save", true, defaultValue = "blacklist"), + AUTO_OPEN_SNAPS("auto_open_snaps", true, configNotices = arrayOf(FeatureNotice.BAN_RISK, FeatureNotice.UNSTABLE), defaultValue = null), UNSAVEABLE_MESSAGES("unsaveable_messages", true, configNotices = arrayOf(FeatureNotice.REQUIRE_NATIVE_HOOKS), defaultValue = null), HIDE_FRIEND_FEED("hide_friend_feed", false, showInFriendMenu = false), E2E_ENCRYPTION("e2e_encryption", false), diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/FeatureManager.kt @@ -128,6 +128,7 @@ class FeatureManager( BetterLocation(), MediaFilePicker(), HideActiveMusic(), + AutoOpenSnaps(), ) initializeFeatures() diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AutoOpenSnaps.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/AutoOpenSnaps.kt @@ -0,0 +1,121 @@ +package me.rhunk.snapenhance.core.features.impl.experiments + +import android.app.Notification +import android.app.NotificationChannel +import android.app.NotificationManager +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import me.rhunk.snapenhance.common.data.ContentType +import me.rhunk.snapenhance.common.data.MessageUpdate +import me.rhunk.snapenhance.common.data.MessagingRuleType +import me.rhunk.snapenhance.core.event.events.impl.BuildMessageEvent +import me.rhunk.snapenhance.core.features.FeatureLoadParams +import me.rhunk.snapenhance.core.features.MessagingRuleFeature +import me.rhunk.snapenhance.core.features.impl.messaging.Messaging +import java.util.concurrent.atomic.AtomicInteger +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine +import kotlin.random.Random + +class AutoOpenSnaps: MessagingRuleFeature("Auto Open Snaps", MessagingRuleType.AUTO_OPEN_SNAPS, loadParams = FeatureLoadParams.INIT_SYNC) { + private val snapQueue = MutableSharedFlow<Pair<String, Long>>() + private var snapQueueSize = AtomicInteger(0) + private val openedSnaps = mutableListOf<Long>() + + private val notificationManager by lazy { + context.androidContext.getSystemService(NotificationManager::class.java) + } + + private val notificationId by lazy { Random.nextInt() } + + private val channelId by lazy { + "auto_open_snaps".also { + notificationManager.createNotificationChannel( + NotificationChannel(it, context.translation["auto_open_snaps.title"], NotificationManager.IMPORTANCE_LOW) + ) + } + } + + private fun sendStatusNotification(count: Int) { + notificationManager.notify( + notificationId, + Notification.Builder(context.androidContext, channelId) + .setContentTitle(context.translation["auto_open_snaps.title"]) + .setContentText(context.translation.format("auto_open_snaps.notification_content", "count" to count.toString())) + .setSmallIcon(android.R.drawable.ic_menu_view) + .setProgress(0, 0, true) + .build().apply { + flags = flags or Notification.FLAG_ONLY_ALERT_ONCE + } + ) + } + + override fun init() { + if (getRuleState() == null) return + val messaging = context.feature(Messaging::class) + + context.coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { + snapQueue.collect { (conversationId, messageId) -> + snapQueueSize.addAndGet(-1) + delay(Random.nextLong(50, 100)) + var result: String? + + for (i in 0..5) { + while (context.isMainActivityPaused || messaging.conversationManager == null) { + delay(2000) + } + + result = suspendCoroutine { continuation -> + runCatching { + messaging.conversationManager?.updateMessage(conversationId, messageId, MessageUpdate.READ) { result -> + continuation.resume(result) + } + }.getOrNull() ?: continuation.resume("ConversationManager is null") + } + + if (result != null && result != "DUPLICATEREQUEST") { + context.log.warn("Failed to mark snap as read, retrying in 3 second") + delay(3000) + continue + } + break + } + + if (snapQueueSize.get() <= 5) { + notificationManager.cancel(notificationId) + synchronized(openedSnaps) { + openedSnaps.clear() + } + } else { + sendStatusNotification(openedSnaps.size) + } + } + } + + context.event.subscribe(BuildMessageEvent::class, priority = 103) { event -> + val conversationId = event.message.messageDescriptor?.conversationId?.toString() ?: return@subscribe + val clientMessageId = event.message.messageDescriptor?.messageId ?: return@subscribe + + if ( + event.message.messageContent?.contentType != ContentType.SNAP || + event.message.messageMetadata?.openedBy?.any { it.toString() == context.database.myUserId } == true + ) { + return@subscribe + } + + context.coroutineScope.launch { + if (!canUseRule(conversationId)) return@launch + synchronized(openedSnaps) { + if (openedSnaps.contains(clientMessageId)) { + return@launch + } + openedSnaps.add(clientMessageId) + } + snapQueueSize.addAndGet(1) + snapQueue.emit(conversationId to clientMessageId) + } + } + } +}+ \ No newline at end of file