feat: friend mutation observer

- bitmoji, birthday changes
This commit is contained in:
rhunk 2024-04-19 23:05:28 +02:00
parent 343159ee5b
commit 95eb4e89f6
3 changed files with 106 additions and 28 deletions

View File

@ -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": {

View File

@ -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",

View File

@ -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 (databaseLinkType == FriendLinkType.MUTUAL && !friend.has("fidelius_info")) {
sendFriendRemoveNotification(databaseFriend.displayName, mutableUsername)
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 (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 {