commit 95eb4e89f634707ca668740a60940d2898ade9ab
parent 343159ee5b934da8865e2c0db18e2693f5c824ad
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date: Fri, 19 Apr 2024 23:05:28 +0200
feat: friend mutation observer
- bitmoji, birthday changes
Diffstat:
3 files changed, 106 insertions(+), 28 deletions(-)
diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json
@@ -538,9 +538,9 @@
"name": "Instant Delete",
"description": "Removes the confirmation dialog when deleting messages"
},
- "relationship_notifier": {
- "name": "Relationship Notifier",
- "description": "Notifies you when someone removes you as a friend"
+ "friend_mutation_notifier": {
+ "name": "Friend Mutation Notifier",
+ "description": "Notifies you when something changes in a friend's profile"
},
"better_notifications": {
"name": "Better Notifications",
@@ -1114,6 +1114,14 @@
"auto_mark_as_read": {
"conversation_read": "Mark conversation as read when sending a message",
"snap_reply": "Mark snaps as read when replying to them"
+ },
+ "friend_mutation_observer": {
+ "remove_friend": "Notify when someone removes you as a friend",
+ "birthday_changes": "Notify when someone changes their birthday",
+ "bitmoji_selfie_changes": "Notify when someone changes their Bitmoji selfie",
+ "bitmoji_avatar_changes": "Notify when someone changes their Bitmoji avatar",
+ "bitmoji_background_changes": "Notify when someone changes their Bitmoji background",
+ "bitmoji_scene_changes": "Notify when someone changes their Bitmoji scene"
}
}
},
@@ -1382,9 +1390,14 @@
"friend_mutation_observer": {
"notification_channel_name": "Friend Mutation Observer",
- "removed_friend_notification_title": "Friend Removed",
- "removed_friend_notification_content": "{username} has removed you as a friend",
- "removed_friend_notification_content_with_display_name": "{displayName} ({username}) has removed you as a friend"
+ "friend_removed": "{username} has removed you as a friend",
+ "birthday_removed": "{username} has removed their birthday ({birthday})",
+ "birthday_added": "{username} has added their birthday ({birthday})",
+ "birthday_changed": "{username} has changed their birthday from {oldBirthday} to {newBirthday}",
+ "bitmoji_selfie_changed": "{username} has changed their Bitmoji selfie",
+ "bitmoji_avatar_changed": "{username} has changed their Bitmoji avatar",
+ "bitmoji_background_changed": "{username} has changed their Bitmoji background",
+ "bitmoji_scene_changed": "{username} has changed their Bitmoji scene"
},
"suspend_location_updates": {
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
@@ -73,7 +73,14 @@ class MessagingTweaks : ConfigContainer() {
nativeHooks()
}
val instantDelete = boolean("instant_delete") { requireRestart() }
- val relationshipNotifier = boolean("relationship_notifier") { requireRestart() }
+ val friendMutationObserver = multiple("friend_mutation_observer",
+ "remove_friend",
+ "birthday_changes",
+ "bitmoji_selfie_changes",
+ "bitmoji_avatar_changes",
+ "bitmoji_background_changes",
+ "bitmoji_scene_changes",
+ ) { requireRestart() }
val betterNotifications = multiple("better_notifications",
"chat_preview",
"media_preview",
diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/FriendMutationObserver.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/FriendMutationObserver.kt
@@ -7,11 +7,13 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.WarningAmber
import com.google.gson.JsonObject
import me.rhunk.snapenhance.common.data.FriendLinkType
+import me.rhunk.snapenhance.common.database.impl.FriendInfo
import me.rhunk.snapenhance.core.event.events.impl.NetworkApiRequestEvent
import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.features.FeatureLoadParams
import me.rhunk.snapenhance.core.util.EvictingMap
import java.io.InputStreamReader
+import java.util.Calendar
class FriendMutationObserver: Feature("FriendMutationObserver", loadParams = FeatureLoadParams.INIT_SYNC) {
private val translation by lazy { context.translation.getCategory("friend_mutation_observer") }
@@ -34,29 +36,45 @@ class FriendMutationObserver: Feature("FriendMutationObserver", loadParams = Fea
return addSourceCache[userId]
}
- private fun sendFriendRemoveNotification(displayName: String?, username: String) {
- val contentText = (if (displayName != null)
- translation.format("removed_friend_notification_content_with_display_name", "displayName" to displayName, "username" to username)
- else translation.format("removed_friend_notification_content", "username" to username))
-
+ private fun sendWarnNotification(
+ contentText: String
+ ) {
notificationManager.notify(System.nanoTime().toInt(),
Notification.Builder(context.androidContext, channelId)
- .setSmallIcon(android.R.drawable.ic_dialog_alert)
- .setContentTitle(translation["removed_friend_notification_title"])
- .setContentText(contentText)
- .setShowWhen(true)
- .setWhen(System.currentTimeMillis())
- .build()
+ .setSmallIcon(android.R.drawable.ic_dialog_alert)
+ .setContentTitle(translation["notification_channel_name"])
+ .setContentText(contentText)
+ .setShowWhen(true)
+ .setWhen(System.currentTimeMillis())
+ .build()
)
context.inAppOverlay.showStatusToast(
Icons.Default.WarningAmber,
contentText,
- durationMs = 6000
+ durationMs = 7000
)
}
+ private fun formatUsername(friendInfo: FriendInfo): String {
+ return friendInfo.displayName?.takeIf { it.isNotBlank() }?.let {
+ "$it (${friendInfo.mutableUsername})"
+ } ?: friendInfo.mutableUsername ?: ""
+ }
+
+ private fun prettyPrintBirthday(month: Int, day: Int): String {
+ val calendar = Calendar.getInstance()
+ calendar[Calendar.MONTH] = month
+ return calendar.getDisplayName(
+ Calendar.MONTH,
+ Calendar.LONG,
+ context.translation.loadedLocale
+ )?.toString() + " " + day
+ }
+
override fun init() {
+ val config by context.config.messaging.friendMutationObserver
+
context.event.subscribe(NetworkApiRequestEvent::class) { event ->
if (!event.url.contains("ami/friends")) return@subscribe
event.onSuccess { buffer ->
@@ -74,20 +92,60 @@ class FriendMutationObserver: Feature("FriendMutationObserver", loadParams = Fea
}
}
- if (context.config.messaging.relationshipNotifier.get()) {
- jsonObject.getAsJsonArray("friends").map { it.asJsonObject }.forEach { friend ->
+ if (config.isEmpty()) return@runCatching
+
+ jsonObject.getAsJsonArray("friends").map { it.asJsonObject }.forEach { friend ->
+ runCatching {
val userId = friend.get("user_id")?.asString
if (userId == context.database.myUserId) return@forEach
- val direction = friend.get("direction")?.asString
- if (direction != "OUTGOING") return@forEach
-
val databaseFriend = context.database.getFriendInfo(userId ?: return@forEach) ?: return@forEach
- val mutableUsername = friend.get("mutable_username").asString
- val databaseLinkType = FriendLinkType.fromValue(databaseFriend.friendLinkType)
+ if (FriendLinkType.fromValue(databaseFriend.friendLinkType) != FriendLinkType.MUTUAL) return@forEach
+
+ if (config.contains("remove_friend") && friend.get("direction")?.asString == "OUTGOING" && !friend.has("fidelius_info")) {
+ sendWarnNotification(translation.format("friend_removed", "username" to formatUsername(databaseFriend)))
+ return@forEach
+ }
+
+ if (config.contains("birthday_changes") &&
+ databaseFriend.birthday.takeIf { it != 0L }?.let {
+ ((it shr 32).toInt()).toString().padStart(2, '0') + "-" + (it.toInt()).toString().padStart(2, '0')
+ } != friend.get("birthday")?.asString
+ ) {
+ val oldBirthday = databaseFriend.birthday.takeIf { it != 0L }?.let {
+ prettyPrintBirthday((it shr 32).toInt(), it.toInt())
+ }
+
+ if (!friend.has("birthday")) {
+ sendWarnNotification(translation.format("birthday_removed", "username" to formatUsername(databaseFriend), "birthday" to oldBirthday.orEmpty()))
+ } else {
+ val newBirthday = friend.get("birthday")?.asString?.split("-")?.let {
+ prettyPrintBirthday(it[0].toInt(), it[1].toInt())
+ }
+ if (oldBirthday == null) {
+ sendWarnNotification(translation.format("birthday_added", "username" to formatUsername(databaseFriend), "birthday" to newBirthday.orEmpty()))
+ } else {
+ sendWarnNotification(translation.format("birthday_changed", "username" to formatUsername(databaseFriend), "oldBirthday" to oldBirthday, "newBirthday" to newBirthday.orEmpty()))
+ }
+ }
+ }
+
+ if (config.contains("bitmoji_avatar_changes") && databaseFriend.bitmojiSelfieId != friend.get("bitmoji_avatar_id")?.asString) {
+ sendWarnNotification(translation.format("bitmoji_avatar_changed", "username" to formatUsername(databaseFriend)))
+ }
+
+ if (config.contains("bitmoji_selfie_changes") && databaseFriend.bitmojiAvatarId != friend.get("bitmoji_selfie_id")?.asString) {
+ sendWarnNotification(translation.format("bitmoji_selfie_changed", "username" to formatUsername(databaseFriend)))
+ }
+
+ if (config.contains("bitmoji_background_changes") && databaseFriend.bitmojiBackgroundId != friend.get("bitmoji_background_id")?.asString) {
+ sendWarnNotification(translation.format("bitmoji_background_changed", "username" to formatUsername(databaseFriend)))
+ }
- if (databaseLinkType == FriendLinkType.MUTUAL && !friend.has("fidelius_info")) {
- sendFriendRemoveNotification(databaseFriend.displayName, mutableUsername)
+ if (config.contains("bitmoji_scene_changes") && databaseFriend.bitmojiSceneId != friend.get("bitmoji_scene_id")?.asString) {
+ sendWarnNotification(translation.format("bitmoji_scene_changed", "username" to formatUsername(databaseFriend)))
}
+ }.onFailure {
+ context.log.error("Failed to process friend", it)
}
}
}.onFailure {