diff --git a/app/libs/snapmod.jar b/app/libs/snapmod.jar index c8a8f77..f56527d 100644 Binary files a/app/libs/snapmod.jar and b/app/libs/snapmod.jar differ diff --git a/app/src/main/java/xyz/rodit/snapmod/arroyo/ArroyoReader.kt b/app/src/main/java/xyz/rodit/snapmod/arroyo/ArroyoReader.kt new file mode 100644 index 0000000..1c6c8ba --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/arroyo/ArroyoReader.kt @@ -0,0 +1,63 @@ +package xyz.rodit.snapmod.arroyo + +import android.content.Context +import android.database.sqlite.SQLiteDatabase +import android.util.Base64 +import xyz.rodit.snapmod.logging.log +import xyz.rodit.snapmod.util.ProtoReader +import java.io.File + +class ArroyoReader(private val context: Context) { + + fun getMessageContent(conversationId: String, messageId: String): String? { + val blob = getMessageBlob(conversationId, messageId) ?: return null + return followProtoString(blob, 4, 4, 2, 1) + } + + fun getKeyAndIv(conversationId: String, messageId: String): Pair? { + val blob = getMessageBlob(conversationId, messageId) ?: return null + val key = followProtoString(blob, 4, 4, 3, 3, 5, 1, 1, 4, 1) ?: return null + val iv = followProtoString(blob, 4, 4, 3, 3, 5, 1, 1, 4, 2) ?: return null + return Base64.decode(key, Base64.DEFAULT) to Base64.decode(iv, Base64.DEFAULT) + } + + fun getSnapKeyAndIv(conversationId: String, messageId: String): Pair? { + val blob = getMessageBlob(conversationId, messageId) ?: return null + val key = followProto(blob, 4, 4, 11, 5, 1, 1, 19, 1) ?: return null + val iv = followProto(blob, 4, 4, 11, 5, 1, 1, 19, 2) ?: return null + return key to iv + } + + private fun followProtoString(data: ByteArray, vararg indices: Int): String? { + val proto = followProto(data, *indices) + return if (proto != null) String(proto) else null + } + + private fun followProto(data: ByteArray, vararg indices: Int): ByteArray? { + var current = data + indices.forEach { i -> + val parts = ProtoReader(current).read() + current = parts.firstOrNull { it.index == i }?.value ?: return null + } + + return current + } + + private fun getMessageBlob(conversationId: String, messageId: String): ByteArray? { + SQLiteDatabase.openDatabase( + File(context.filesDir, "../databases/arroyo.db").path, + null, + 0 + ).use { + it.rawQuery( + "SELECT message_content FROM conversation_message WHERE client_conversation_id='$conversationId' AND server_message_id=$messageId", + null + ).use { cursor -> + if (cursor.moveToFirst()) return cursor.getBlob(0) + else log.debug("No result in db.") + } + } + + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt b/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt index 3c216c3..7d615e8 100644 --- a/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt +++ b/app/src/main/java/xyz/rodit/snapmod/features/FeatureManager.kt @@ -6,6 +6,8 @@ import xyz.rodit.snapmod.features.friendsfeed.FeedModifier import xyz.rodit.snapmod.features.info.AdditionalFriendInfo import xyz.rodit.snapmod.features.info.NetworkLogging import xyz.rodit.snapmod.features.messagemenu.MessageMenuModifier +import xyz.rodit.snapmod.features.notifications.FilterTypes +import xyz.rodit.snapmod.features.notifications.ShowMessageContent import xyz.rodit.snapmod.features.opera.OperaModelModifier import xyz.rodit.snapmod.features.saving.ChatSaving import xyz.rodit.snapmod.features.saving.PublicProfileSaving @@ -35,6 +37,10 @@ class FeatureManager(context: FeatureContext) : Contextual(context) { // Message context menu add(::MessageMenuModifier) + // Notifications + add(::FilterTypes) + add(::ShowMessageContent) + // Opera (story/snap view) add(::OperaModelModifier) diff --git a/app/src/main/java/xyz/rodit/snapmod/features/notifications/FilterTypes.kt b/app/src/main/java/xyz/rodit/snapmod/features/notifications/FilterTypes.kt new file mode 100644 index 0000000..344dc51 --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/notifications/FilterTypes.kt @@ -0,0 +1,34 @@ +package xyz.rodit.snapmod.features.notifications + +import xyz.rodit.snapmod.features.Feature +import xyz.rodit.snapmod.features.FeatureContext +import xyz.rodit.snapmod.mappings.NotificationData +import xyz.rodit.snapmod.mappings.NotificationHandler +import xyz.rodit.snapmod.util.before +import xyz.rodit.snapmod.util.getList + +class FilterTypes(context: FeatureContext) : Feature(context) { + + private val hiddenTypes = hashSetOf() + + override fun onConfigLoaded(first: Boolean) { + hiddenTypes.clear() + hiddenTypes.addAll(context.config.getList("filtered_notification_types")) + } + + override fun performHooks() { + NotificationHandler.handle.before { + if (hiddenTypes.isEmpty()) return@before + + val handler = NotificationHandler.wrap(it.thisObject) + val data = NotificationData.wrap(handler.data) + val bundle = data.bundle + + val type = + bundle.getString("type") ?: bundle.getString("n_key")?.split('~')?.get(0) ?: "" + if (hiddenTypes.contains(type.lowercase())) { + it.result = null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/features/notifications/ShowMessageContent.kt b/app/src/main/java/xyz/rodit/snapmod/features/notifications/ShowMessageContent.kt new file mode 100644 index 0000000..7625d2b --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/features/notifications/ShowMessageContent.kt @@ -0,0 +1,185 @@ +package xyz.rodit.snapmod.features.notifications + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.media.ThumbnailUtils +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import android.util.Size +import androidx.core.app.NotificationCompat +import com.google.gson.Gson +import com.google.gson.JsonArray +import xyz.rodit.snapmod.Shared +import xyz.rodit.snapmod.arroyo.ArroyoReader +import xyz.rodit.snapmod.features.Feature +import xyz.rodit.snapmod.features.FeatureContext +import xyz.rodit.snapmod.logging.log +import xyz.rodit.snapmod.mappings.* +import xyz.rodit.snapmod.util.before +import java.io.File +import java.io.InputStream +import java.net.URL + +private const val CHANNEL_ID = "snapmod_notifications" + +private val imageSig = listOf( + "ffd8ff", // jpeg + "1a45dfa3", // webm + "89504e47", // png +) + +class ShowMessageContent(context: FeatureContext) : Feature(context) { + + private val arroyoReader = ArroyoReader(context.appContext) + private val gson: Gson = Gson() + + private val notifications + get() = context.appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + + private var notificationSmallIcon = 0 + private var notificationId = 0 + + override fun init() { + notificationSmallIcon = + context.appContext.resources.getIdentifier("icon_v6", "mipmap", Shared.SNAPCHAT_PACKAGE) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + notifications.createNotificationChannel( + NotificationChannel( + CHANNEL_ID, + "SnapMod Custom Notifications", + NotificationManager.IMPORTANCE_HIGH + ) + ) + } + } + + override fun performHooks() { + NotificationHandler.handle.before(context, "show_notification_content") { + val handler = NotificationHandler.wrap(it.thisObject) + val data = NotificationData.wrap(handler.data) + val messageHandler = MessagingNotificationHandler.wrap(handler.handler) + val idProvider = ConversationIdProvider.wrap(handler.conversationIdProvider) + val bundle = data.bundle + val type = bundle.get("type") ?: "unknown" + val conversationId = bundle.getString("arroyo_convo_id") + val messageId = bundle.getString("arroyo_message_id") + if (conversationId.isNullOrBlank() || messageId.isNullOrBlank()) return@before + bundle.getString("media_info")?.let { mediaInfo -> + if (!context.config.getBoolean("show_notification_media_previews")) return@before + + val snap = type == "snap" + val (key, iv) = (if (snap) + arroyoReader.getSnapKeyAndIv(conversationId, messageId) + else + arroyoReader.getKeyAndIv(conversationId, messageId)) ?: return@before + + val media = getDownloadUrls(mediaInfo) + val crypt = AesCrypto(key, iv) + crypt.decrypt(URL(media[0]).openStream()).use { stream -> + val (bitmap, isImage) = generatePreview(stream) + if (bitmap == null) return@before + + val feedId = messageHandler.conversationRepository.getFeedId(conversationId) + val group = idProvider.conversationIdentifier.group + + val title = (bundle.getString("ab_cnotif_body") ?: "sent Media") + + if (snap) + " (${if (isImage) "Image" else "Video"})" + else + " (Media x ${media.size})" + + val notification = NotificationCompat.Builder(context.appContext, CHANNEL_ID) + .setContentTitle(bundle.getString("sender") ?: "Unknown Sender") + .setContentText(title) + .setSmallIcon(notificationSmallIcon) + .setLargeIcon(bitmap) + .setContentIntent(createContentIntent(feedId, bundle, group)) + .setAutoCancel(true) + .setStyle( + NotificationCompat.BigPictureStyle() + .bigPicture(bitmap) + .bigLargeIcon(null) + ) + .build() + + notifications.notify(notificationId++, notification) + it.result = null + } + + return@before + } + + val content = arroyoReader.getMessageContent(conversationId, messageId) ?: return@before + bundle.putString("ab_cnotif_body", content) + } + } + + private fun getDownloadUrls(mediaInfoJson: String): List { + val json = gson.fromJson(mediaInfoJson, JsonArray::class.java) + return json.map { it.asJsonObject.get("directDownloadUrl").asString }.toList() + } + + private fun createContentIntent(feedId: Long, bundle: Bundle, group: Boolean): PendingIntent { + val conversationId = bundle.getString("arroyo_convo_id") ?: "" + val uri = Uri.parse("snapchat://notification/notification_chat/").buildUpon() + .appendQueryParameter("feed-id", feedId.toString()) + .appendQueryParameter("conversation-id", conversationId) + .appendQueryParameter("is-group", group.toString()) + .appendQueryParameter("source_type", "CHAT") + .build() + val intent = Intent("android.intent.action.VIEW_CHAT", uri) + .setClassName(Shared.SNAPCHAT_PACKAGE, Shared.SNAPCHAT_PACKAGE + ".LandingPageActivity") + .putExtra("messageId", bundle.getString("chat_message_id")) + .putExtra("type", "CHAT") + .putExtra("fromServerNotification", true) + .putExtra("notificationId", bundle.getString("n_id")) + + val flags = PendingIntent.FLAG_IMMUTABLE + return PendingIntent.getActivity(context.appContext, 0, intent, flags) + } + + private fun generatePreview(stream: InputStream): Pair { + val temp = File.createTempFile("snapmod", "tmp", context.appContext.cacheDir) + temp.outputStream().use(stream::copyTo) + + val sig = ByteArray(4) + temp.inputStream().use { it.read(sig) } + val sigString = sig.joinToString("") { "%02x".format(it) } + + val isImage = imageSig.any { sigString.startsWith(it) } + val preview = + if (isImage) BitmapFactory.decodeFile(temp.path) else generateVideoPreview(temp) + + temp.delete() + return preview to isImage + } + + private fun generateVideoPreview(file: File): Bitmap? { + try { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + ThumbnailUtils.createVideoThumbnail( + file, + Size(1024, 1024), + null + ) + } else { + ThumbnailUtils.createVideoThumbnail( + file.path, + MediaStore.Images.Thumbnails.MINI_KIND + ) + } + } catch (e: Exception) { + log.error("Error creating video thumbnail.", e) + } + + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/xyz/rodit/snapmod/util/ProtoReader.kt b/app/src/main/java/xyz/rodit/snapmod/util/ProtoReader.kt new file mode 100644 index 0000000..a26a6f3 --- /dev/null +++ b/app/src/main/java/xyz/rodit/snapmod/util/ProtoReader.kt @@ -0,0 +1,89 @@ +package xyz.rodit.snapmod.util + +import xyz.rodit.snapmod.logging.log +import java.math.BigInteger + +private val BIGINT_2 = BigInteger.valueOf(2) +private const val TYPE_VAR_INT = 0 +private const val TYPE_STRING = 2 + +internal inline infix fun Byte.and(other: Int): Int = toInt() and other + +internal inline infix fun Byte.shl(other: Int): Int = toInt() shl other + +class ProtoReader(private val data: ByteArray) { + + private var position = 0 + private var checkpoint = 0 + + fun read(): List { + val parts = mutableListOf() + + while (position < data.size) { + checkpoint = position + + val varInt = internalReadVarint32() + val type = varInt and 0b111 + val index = varInt shr 3 + + var value = ByteArray(0) + + if (type == TYPE_VAR_INT) { + value = internalReadVarint32().toString().toByteArray() + } else if (type == TYPE_STRING) { + val length = internalReadVarint32() + value = ByteArray(length) + data.copyInto(value, 0, position, position + length) + position += length + } else { + log.error("Unknown protobuf type $type") + } + + parts.add(ProtoPart(index, type, value)) + } + + return parts + } + + private fun readByte(): Byte { + return data[position++] + } + + private fun internalReadVarint32(): Int { + var tmp = readByte() + if (tmp >= 0) { + return tmp.toInt() + } + var result = tmp and 0x7f + tmp = readByte() + if (tmp >= 0) { + result = result or (tmp shl 7) + } else { + result = result or (tmp and 0x7f shl 7) + tmp = readByte() + if (tmp >= 0) { + result = result or (tmp shl 14) + } else { + result = result or (tmp and 0x7f shl 14) + tmp = readByte() + if (tmp >= 0) { + result = result or (tmp shl 21) + } else { + result = result or (tmp and 0x7f shl 21) + tmp = readByte() + result = result or (tmp shl 28) + if (tmp < 0) { + for (i in 0..4) { + if (readByte() >= 0) { + return result + } + } + } + } + } + } + return result + } +} + +data class ProtoPart(val index: Int, val type: Int, val value: ByteArray) \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 06eafc9..8af9032 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -41,6 +41,18 @@ for_you + + Chats + Snaps + Typing + + + + chat + snap + typing + + Warnings Errors diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8f31995..1786141 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ Tweaks Snaps Downloads + Notifications Location Sharing Information @@ -136,4 +137,13 @@ Enable New Voice Notes Force enables the new voice note UI. + + Show Message In Notifications + Displays message content in notifications. + + Show Media Previews + Shows previews in the notifications for non-text media (images and videos). Note, this requires the entire media to be downloaded so this will consume data when notifications are received. + + Filtered Message Types + Notifications will not be shown for filtered message types. \ No newline at end of file diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 7882550..c5643cc 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -86,18 +86,6 @@ app:title="@string/hide_read_title" app:summary="@string/hide_read_description" app:iconSpaceReserved="false"/> - - - - - - - - + + () { - .string "NONE", bind field NONE next reference; - .string "SENT", bind field SENT next reference; - .string "DELIVERED", bind field DELIVERED next reference; - .string "VIEWED", bind field VIEWED next reference; - .string "SCREENSHOT", bind field SCREENSHOT next reference; - .string "PENDING", bind field PENDING next reference; - } + [late] static this .fields { NONE, SENT, DELIVERED, VIEWED, SCREENSHOT, PENDING } } -[certain] +[certain, obfuscated] enum SnapPlaybackStatus { - static void $() { - .string "PLAYABLE", bind field PLAYABLE next reference; - .string "VIEWEDREPLAYABLE", bind field VIEWEDREPLAYABLE next reference; - .string "PLAYING", bind field PLAYING next reference; - .string "VIEWEDNOTREPLAYABLE", bind field VIEWEDNOTREPLAYABLE next reference; - .string "SAVED", bind field SAVED next reference; - .string "NONE", bind field NONE next reference; - } + [late] static this .fields { PLAYABLE, VIEWEDREPLAYABLE, PLAYING, VIEWEDNOTREPLAYABLE, SAVED, NONE } } -[certain] +[certain, obfuscated] enum SaveType { - static void $() { - .string "SNAPCHAT_ALBUM", bind field SNAPCHAT_ALBUM next reference; - .string "SPECTACLES_ALBUM", bind field SPECTACLES_ALBUM next reference; - .string "EXTERNAL_APPS", bind field EXTERNAL_APPS next reference; - .string "SMS", bind field SMS next reference; - .string "EMAIL", bind field EMAIL next reference; - } + [late] static this .fields { SNAPCHAT_ALBUM, SPECTACLES_ALBUM, EXTERNAL_APPS, SMS, EMAIL } } -[certain] +[certain, obfuscated] enum ChatLayouts { - int layoutId; - - static void $() { - .string "UNKNOWN", bind field SNAPCHAT_ALBUM next reference; - .string "TEXT_WITH_MEDIA_CARDS", bind field TEXT_WITH_MEDIA_CARDS next reference; - .string "SNAP", bind field SNAP next reference; - .string "CHAT_MEDIA", bind field CHAT_MEDIA next reference; - .string "MEMORIES_STORY", bind field MEMORIES_STORY next reference; - .string "TEXT_STORY_REPLY", bind field TEXT_STORY_REPLY next reference; - .string "AUDIO_NOTE", bind field AUDIO_NOTE next reference; - .string "ERASE", bind field ERASE next reference; - .string "SAVED_SNAP", bind field SAVED_SNAP next reference; - .string "PLUGIN", bind field PLUGIN next reference; - .string "PLUGIN_STATUS", bind field PLUGIN_STATUS next reference; + [late] static this .fields { + UNKNOWN, + TEXT_WITH_MEDIA_CARDS, + SNAP, + CHAT_MEDIA, + MEMORIES_STORY, + TEXT_STORY_REPLY, + AUDIO_NOTE, + ERASE, + SAVED_SNAP, + PLUGIN, + PLUGIN_STATUS } + + int layoutId; } -[certain] +[certain, obfuscated] enum SnapMediaType { - @SerializedName("text") - static this TEXT; - @SerializedName("media") - static this MEDIA; - @SerializedName("sticker") - static this STICKER; - @SerializedName("media_v3") - static this MEDIA_V3; - @SerializedName("media_v4") - static this MEDIA_V4; + [late] static this .fields { TEXT, MEDIA, STICKER, MEDIA_V3, MEDIA_V4 } String name; } @@ -297,16 +270,10 @@ class MediaReference { } } -[certain] +[certain, obfuscated] enum MediaType { - static void $() { - .string "IMAGE", bind field IMAGE next reference; - .string "VIDEO", bind field VIDEO next reference; - .string "VIDEO_NO_SOUND", bind field VIDEO_NO_SOUND next reference; - .string "AUDIO", bind field AUDIO next reference; - .string "WEB", bind field WEB next reference; - } + [late] static this .fields { IMAGE, VIDEO, VIDEO_NO_SOUND, AUDIO, WEB } } [late] @@ -521,13 +488,10 @@ class FriendProfilePageData { } } -[certain] +[certain, obfuscated] enum ProfileSectionType { - static void $() { - .string "FOOTER_INFO_ITEM", bind field FOOTER_INFO_ITEM next reference; - .string "IDENTITY_CAROUSEL"; - } + [late] static this .fields { FOOTER_INFO_ITEM, IDENTITY_CAROUSEL } } class FooterInfoItem { @@ -567,23 +531,16 @@ class ChatContext { } } -[certain] +[certain, obfuscated] enum ChatMenuItemType { - [late] static this COPY; - [late] static this SAVE_IN_CHAT; - [late] static this UNSAVE_IN_CHAT; - [late] static this SAVE_TO_CAMERA_ROLL; - [late] static this ERASE_QUOTE; - [late] static this SNAP_REPLY; - - static void $() { - .string "COPY", bind field COPY next reference; - .string "SAVE_IN_CHAT", bind field SAVE_IN_CHAT next reference; - .string "UNSAVE_IN_CHAT", bind field UNSAVE_IN_CHAT next reference; - .string "SAVE_TO_CAMERA_ROLL", bind field SAVE_TO_CAMERA_ROLL next reference; - .string "ERASE_QUOTE", bind field ERASE_QUOTE next reference; - .string "SNAP_REPLY", bind field SNAP_REPLY next reference; + [late] static this .fields { + COPY, + SAVE_IN_CHAT, + UNSAVE_IN_CHAT, + SAVE_TO_CAMERA_ROLL, + ERASE_QUOTE, + SNAP_REPLY } } @@ -608,21 +565,17 @@ class BitmojiUriHandler { } } -[certain] +[certain, obfuscated] enum FriendAddMethod { - @com.google.gson.annotations.SerializedName("ADDED_BY_USERNAME") - static * ADDED_BY_USERNAME; - @com.google.gson.annotations.SerializedName("ADDED_BY_ADDED_ME_BACK") - static * ADDED_BY_ADDED_ME_BACK; - @com.google.gson.annotations.SerializedName("ADDED_BY_SUGGESTED") - static * ADDED_BY_SUGGESTED; - @com.google.gson.annotations.SerializedName("ADDED_BY_SHARED_USERNAME") - static * ADDED_BY_SHARED_USERNAME; - @com.google.gson.annotations.SerializedName("ADDED_BY_SHARED_STORY") - static * ADDED_BY_SHARED_STORY; - @com.google.gson.annotations.SerializedName("ADDED_BY_GROUP_CHAT") - static * ADDED_BY_GROUP_CHAT; + [late] static this .fields { + ADDED_BY_USERNAME, + ADDED_BY_ADDED_ME_BACK, + ADDED_BY_SUGGESTED, + ADDED_BY_SHARED_USERNAME, + ADDED_BY_SHARED_STORY, + ADDED_BY_GROUP_CHAT + } } [certain] @@ -693,17 +646,17 @@ class OperaActionMenuOptionViewModel { } } -[certain] +[certain, obfuscated] enum OperaContextAction { - static void $() { - .string "SEND", bind field SEND next reference; - .string "EDIT", bind field EDIT next reference; - .string "EXPORT", bind field EXPORT next reference; - .string "IN_APP_REPORT", bind field IN_APP_REPORT next reference; - .string "REPORT_AD", bind field REPORT_AD next reference; - .string "HIDE_AD", bind field HIDE_AD next reference; - .string "SAVE", bind field SAVE next reference; + [late] static this .fields { + SEND, + EDIT, + EXPORT, + IN_APP_REPORT, + REPORT_AD, + HIDE_AD, + SAVE } } @@ -812,21 +765,21 @@ class SendAndRecycleProcessor { } } -[certain] +[certain, obfuscated] enum MessageMediaSource { - [not] int i0; - - static void $() { - .string "CAMERA", bind field CAMERA next reference; - .string "CHAT", bind field CHAT next reference; - .string "IN_APP_NOTIFICATION", bind field IN_APP_NOTIFICATION next reference; - .string "DIRECT_SHARE", bind field DIRECT_SHARE next reference; - .string "FEED_REPLY_BUTTON", bind field FEED_REPLY_BUTTON next reference; - .string "GALLERY", bind field GALLERY next reference; - .string "GALLERY_STORY", bind field GALLERY_STORY next reference; - .string "CAMERA_ROLL", bind field CAMERA_ROLL next reference; + [late] static this .fields { + CAMERA, + CHAT, + IN_APP_NOTIFICATION, + DIRECT_SHARE, + FEED_REPLY_BUTTON, + GALLERY, + GALLERY_STORY, + CAMERA_ROLL } + + [not] int i0; } class MessageSenderCrossroad { @@ -1292,29 +1245,16 @@ class ActionMenuActionModel { } } -[certain] +[certain, obfuscated] enum ActionMenuOptionItemType { - [late] static this OPTION_ITEM; - [late] static this OPTION_ITEM_TOGGLE; - [late] static this SIMPLE_OPTION_ITEM; - [late] static this SPINNER_OPTION_ITEM; - [late] static this SUBTITLE_OPTION_ITEM; - [late] static this FEED_OPTION_ITEM; - - static void $() { - [strict] - .string "OPTION_ITEM", bind field OPTION_ITEM next reference; - [strict] - .string "OPTION_ITEM_TOGGLE", bind field OPTION_ITEM_TOGGLE next reference; - [strict] - .string "SIMPLE_OPTION_ITEM", bind field SIMPLE_OPTION_ITEM next reference; - [strict] - .string "SPINNER_OPTION_ITEM", bind field SPINNER_OPTION_ITEM next reference; - [strict] - .string "SUBTITLE_OPTION_ITEM", bind field SUBTITLE_OPTION_ITEM next reference; - [strict] - .string "FEED_OPTION_ITEM", bind field FEED_OPTION_ITEM next reference; + [late] static this .fields { + OPTION_ITEM, + OPTION_ITEM_TOGGLE, + SIMPLE_OPTION_ITEM, + SPINNER_OPTION_ITEM, + SUBTITLE_OPTION_ITEM, + FEED_OPTION_ITEM } } @@ -1340,26 +1280,15 @@ class ActionMenuOptionToggleItemViewModel { } } -[certain, discard] +[certain, discard, obfuscated] enum ChatActionType { - [late] static this CHAT_BUTTON_CLICK; - [late] static this SNAP_BUTTON_CLICK; - [late] static this AUDIO_CALL_BUTTON_CLICK; - [late] static this VIDEO_CALL_BUTTON_CLICK; - [late] static this SETTINGS_CLICK; - - static void $() { - [strict] - .string "CHAT_BUTTON_CLICK", bind field CHAT_BUTTON_CLICK next reference; - [strict] - .string "SNAP_BUTTON_CLICK", bind field SNAP_BUTTON_CLICK next reference; - [strict] - .string "AUDIO_CALL_BUTTON_CLICK", bind field AUDIO_CALL_BUTTON_CLICK next reference; - [strict] - .string "VIDEO_CALL_BUTTON_CLICK", bind field VIDEO_CALL_BUTTON_CLICK next reference; - [strict] - .string "SETTINGS_CLICK", bind field SETTINGS_CLICK next reference; + [late] static this .fields { + CHAT_BUTTON_CLICK, + SNAP_BUTTON_CLICK, + AUDIO_CALL_BUTTON_CLICK, + VIDEO_CALL_BUTTON_CLICK, + SETTINGS_CLICK } } @@ -1404,24 +1333,10 @@ class FriendChatActionHandler { } } -[late] +[late, obfuscated] enum ConfigValueType { - [late] static this BOOLEAN; - [late] static this INTEGER; - [late] static this LONG; - [late] static this FLOAT; - [late] static this DOUBLE; - [late] static this STRING; - - static void $() { - .string "BOOLEAN", bind field BOOLEAN next reference; - .string "INTEGER", bind field INTEGER next reference; - .string "LONG", bind field LONG next reference; - .string "FLOAT", bind field FLOAT next reference; - .string "DOUBLE", bind field DOUBLE next reference; - .string "STRING", bind field STRING next reference; - } + [late] static this .fields { BOOLEAN, INTEGER, LONG, FLOAT, DOUBLE, STRING } } [late] @@ -1438,15 +1353,10 @@ interface ConfigKeyBase { String getName() } +[obfuscated] enum AdConfigKey implements #ConfigKeyBase { - [late] static this GLOBAL_MIN_TIME_BETWEEN_ADS; - [late] static this GLOBAL_MIN_SNAPS_BETWEEN_ADS; - - static void $() { - .string "GLOBAL_MIN_TIME_BETWEEN_ADS", bind field GLOBAL_MIN_TIME_BETWEEN_ADS next reference; - .string "GLOBAL_MIN_SNAPS_BETWEEN_ADS", bind field GLOBAL_MIN_SNAPS_BETWEEN_ADS next reference; - } + [late] static this .fields { GLOBAL_MIN_TIME_BETWEEN_ADS, GLOBAL_MIN_SNAPS_BETWEEN_ADS } } class CompositeConfigurationProvider { @@ -1719,29 +1629,18 @@ class VideoLengthChecker { } } -[certain] +[certain, obfuscated] enum ChatCommandSource { - @SerializedName("CHAT") - static this CHAT; - [discard] - @SerializedName("CAMERA") - static this CAMERA; - [discard] - @SerializedName("DISCOVER") - static this DISCOVER; - [discard] - @SerializedName("PROFILE") - static this PROFILE; - [discard] - @SerializedName("PUBLIC_PROFILE") - static this PUBLIC_PROFILE; - [discard] - @SerializedName("STORY") - static this STORY; - [discard] - @SerializedName("PROFILE_GALLERY_PLAYBACK") - static this PROFILE_GALLERY_PLAYBACK; + [late] static this .fields { + CHAT, + [discard] CAMERA, + [discard] DISCOVER, + [discard] PROFILE, + [discard] PUBLIC_PROFILE, + [discard] STORY, + [discard] PROFILE_GALLERY_PLAYBACK + } } [certain] @@ -1771,4 +1670,104 @@ class ChatModelPlugin { .field !ChatLayouts->!PLUGIN; .field !ChatLayouts->!PLUGIN_STATUS; } +} + +[certain, discard] +class NotificationDisplayData { + + String $toString() { + .string "DisplayData(senderDisplayName="; + .string ", systemText="; + .string ", title="; + } +} + +[certain] +class NotificationData { + + String id; + String key; + String recipientId; + String recipientUsername; + [discard] !NotificationDisplayData ndd0; + long timestamp0; + long timestamp1; + String revokeKey; + Bundle bundle; +} + +[certain, obfuscated, discard] +enum NotificationHandlerStatus { + + [late] static this .fields { + HANDLER_START, + DISPLAY_MODEL_CREATE, + RETURN_EMPTY_RATE_LIMITED + } +} + +[certain] +class ConversationIdentifier { + + String id; + boolean group; + + String $toString() { + .string "ConversationIdentifier(id="; + .string ", isGroup="; + } +} + +[certain] +class ConversationIdProvider { + + void $(*, !NotificationData, ...) + + !ConversationIdentifier getConversationIdentifier() +} + +[certain] +class DefaultConversationsRepository { + + void $(...) { + .string "DefaultConversationsRepository"; + } + + long getFeedId(String) +} + +[certain] +class MessagingNotificationHandler { + + !DefaultConversationsRepository conversationRepository; + + void $(...) { + .string "MessagingNotificationHandler"; + } +} + +[certain] +class NotificationHandler { + + Object conversationIdProvider; + [discard] int a0; + Object data; + Object handler; + + * handle() { + .type !NotificationData; + .field !NotificationHandlerStatus->!RETURN_EMPTY_RATE_LIMITED; + .field !NotificationHandlerStatus->!DISPLAY_MODEL_CREATE; + } +} + +[certain] +class AesCrypto { + + void $(String, String, ...) + void $(byte[], byte[]) + + InputStream encrypt(InputStream) + + InputStream decrypt(InputStream) } \ No newline at end of file