commit a879419fc5418e3407293ce1ad6928941ea62f6f
parent 7938871086d68501335c86aec1baf4d7e3663e32
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Sun, 29 Oct 2023 12:06:12 +0100

feat(core/better_notifications): mark as read

Diffstat:
Mcommon/src/main/assets/lang/en_US.json | 10++++++++++
Mcommon/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/MessagingTweaks.kt | 2+-
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Notifications.kt | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
3 files changed, 65 insertions(+), 9 deletions(-)

diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -597,6 +597,7 @@ "snap": "Show media", "reply_button": "Add reply button", "download_button": "Add download button", + "mark_as_read_button": "Mark as Read button", "group": "Group notifications" }, "friend_feed_menu_buttons": { @@ -782,6 +783,15 @@ "download": "Download" }, + "better_notifications": { + "button": { + "reply": "Reply", + "download": "Download", + "mark_as_read": "Mark as Read" + }, + "stealth_mode_notice": "Can't mark as read in stealth mode" + }, + "profile_picture_downloader": { "button": "Download Profile Picture", "title": "Profile Picture Downloader", 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 @@ -23,7 +23,7 @@ class MessagingTweaks : ConfigContainer() { customOptionTranslationPath = "features.options.notifications" nativeHooks() } - val betterNotifications = multiple("better_notifications", "snap", "chat", "reply_button", "download_button", "group") { requireRestart() } + val betterNotifications = multiple("better_notifications", "snap", "chat", "reply_button", "download_button", "mark_as_read_button", "group") { requireRestart() } val notificationBlacklist = multiple("notification_blacklist", *NotificationType.getIncomingValues().map { it.key }.toTypedArray()) { customOptionTranslationPath = "features.options.notifications" } diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Notifications.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/messaging/Notifications.kt @@ -25,10 +25,9 @@ import me.rhunk.snapenhance.core.features.Feature import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.core.features.impl.downloader.decoder.MessageDecoder -import me.rhunk.snapenhance.core.logger.CoreLogger +import me.rhunk.snapenhance.core.features.impl.spying.StealthMode import me.rhunk.snapenhance.core.util.CallbackBuilder import me.rhunk.snapenhance.core.util.hook.HookStage -import me.rhunk.snapenhance.core.util.hook.Hooker import me.rhunk.snapenhance.core.util.hook.hook import me.rhunk.snapenhance.core.util.ktx.setObjectField import me.rhunk.snapenhance.core.util.media.PreviewUtils @@ -39,6 +38,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN companion object{ const val ACTION_REPLY = "me.rhunk.snapenhance.action.notification.REPLY" const val ACTION_DOWNLOAD = "me.rhunk.snapenhance.action.notification.DOWNLOAD" + const val ACTION_MARK_AS_READ = "me.rhunk.snapenhance.action.notification.MARK_AS_READ" const val SNAPCHAT_NOTIFICATION_GROUP = "snapchat_notification_group" } @@ -64,6 +64,8 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN context.androidContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager } + private val translations by lazy { context.translation.getCategory("better_notifications") } + private val betterNotificationFilter by lazy { context.config.messaging.betterNotifications.get() } @@ -118,19 +120,23 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN actions.add(action) } - newAction("Reply", ACTION_REPLY, { + newAction(translations["button.reply"], ACTION_REPLY, { betterNotificationFilter.contains("reply_button") && contentType == ContentType.CHAT }) { val chatReplyInput = RemoteInput.Builder("chat_reply_input") - .setLabel("Reply") + .setLabel(translations["button.reply"]) .build() it.addRemoteInput(chatReplyInput) } - newAction("Download", ACTION_DOWNLOAD, { + newAction(translations["button.download"], ACTION_DOWNLOAD, { betterNotificationFilter.contains("download_button") && (contentType == ContentType.EXTERNAL_MEDIA || contentType == ContentType.SNAP) }) {} + newAction(translations["button.mark_as_read"], ACTION_MARK_AS_READ, { + betterNotificationFilter.contains("mark_as_read_button") + }) {} + notificationBuilder.setActions(*actions.toTypedArray()) notificationData.notification = notificationBuilder.build() } @@ -141,7 +147,6 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN val conversationId = intent.getStringExtra("conversation_id") ?: return@subscribe val messageId = intent.getLongExtra("message_id", -1) val notificationId = intent.getIntExtra("notification_id", -1) - val notificationManager = event.androidContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val updateNotification: (Int, (Notification) -> Unit) -> Unit = { id, notificationBuilder -> notificationManager.activeNotifications.firstOrNull { it.id == id }?.let { @@ -177,6 +182,47 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN context.longToast(it) } } + ACTION_MARK_AS_READ -> { + runCatching { + if (context.feature(StealthMode::class).canUseRule(conversationId)) { + context.longToast(translations["stealth_mode_notice"]) + return@subscribe + } + + val conversationManager = context.feature(Messaging::class).conversationManager ?: return@subscribe + + context.classCache.conversationManager.methods.first { it.name == "displayedMessages"}?.invoke( + conversationManager, + SnapUUID.fromString(conversationId).instanceNonNull(), + messageId, + CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback")) + .override("onError") { + context.log.error("Failed to mark message as read: ${it.arg(0) as Any}") + context.shortToast("Failed to mark message as read") + }.build() + ) + + val conversationMessage = context.database.getConversationMessageFromId(messageId) ?: return@subscribe + + if (conversationMessage.contentType == ContentType.SNAP.id) { + context.classCache.conversationManager.methods.first { it.name == "updateMessage"}?.invoke( + conversationManager, + SnapUUID.fromString(conversationId).instanceNonNull(), + messageId, + context.classCache.messageUpdateEnum.enumConstants.first { it.toString() == "READ" }, + CallbackBuilder(context.mappings.getMappedClass("callbacks", "Callback")) + .override("onError") { + context.log.error("Failed to open snap: ${it.arg(0) as Any}") + context.shortToast("Failed to open snap") + }.build() + ) + } + }.onFailure { + context.log.error("Failed to mark message as read", it) + context.shortToast("Failed to mark message as read. Check logs for more details") + } + notificationManager.cancel(notificationId) + } else -> return@subscribe } @@ -282,7 +328,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN sendNotificationData(notificationData.copy(notification = notificationBuilder.build()), true) return@onEach }.onFailure { - CoreLogger.xposedLog("Failed to send preview notification", it) + context.log.error("Failed to send preview notification", it) } } } @@ -302,7 +348,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN val fetchConversationWithMessagesCallback = context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") - Hooker.hook(notifyAsUserMethod, HookStage.BEFORE) { param -> + notifyAsUserMethod.hook(HookStage.BEFORE) { param -> val notificationData = NotificationData(param.argNullable(0), param.arg(1), param.arg(2), param.arg(3)) val extras: Bundle = notificationData.notification.extras.getBundle("system_notification_extras")?: return@hook