perf: bridge objects

- bridge oneway methods
- scope sync
This commit is contained in:
rhunk 2024-01-16 00:01:37 +01:00
parent 0994b8f36d
commit 8c71e4af27
16 changed files with 232 additions and 187 deletions

View File

@ -8,6 +8,7 @@ import java.io.ByteArrayOutputStream
plugins { plugins {
alias(libs.plugins.androidApplication) alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid) alias(libs.plugins.kotlinAndroid)
id("kotlin-parcelize")
} }
android { android {

View File

@ -12,9 +12,8 @@ import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.common.data.MessagingFriendInfo import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.data.SocialScope import me.rhunk.snapenhance.common.data.SocialScope
import me.rhunk.snapenhance.common.database.impl.FriendInfo
import me.rhunk.snapenhance.common.logger.LogLevel import me.rhunk.snapenhance.common.logger.LogLevel
import me.rhunk.snapenhance.common.util.SerializableDataObject import me.rhunk.snapenhance.common.util.toParcelable
import me.rhunk.snapenhance.download.DownloadProcessor import me.rhunk.snapenhance.download.DownloadProcessor
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -61,12 +60,12 @@ class BridgeService : Service() {
when (scope) { when (scope) {
SocialScope.FRIEND -> { SocialScope.FRIEND -> {
SerializableDataObject.fromJson<FriendInfo>(syncedObject).let { toParcelable<MessagingFriendInfo>(syncedObject)?.let {
modDatabase.syncFriend(it) modDatabase.syncFriend(it)
} }
} }
SocialScope.GROUP -> { SocialScope.GROUP -> {
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedObject).let { toParcelable<MessagingGroupInfo>(syncedObject)?.let {
modDatabase.syncGroupInfo(it) modDatabase.syncGroupInfo(it)
} }
} }
@ -169,8 +168,8 @@ class BridgeService : Service() {
) { ) {
remoteSideContext.log.verbose("Received ${groups.size} groups and ${friends.size} friends") remoteSideContext.log.verbose("Received ${groups.size} groups and ${friends.size} friends")
remoteSideContext.modDatabase.receiveMessagingDataCallback( remoteSideContext.modDatabase.receiveMessagingDataCallback(
friends.map { SerializableDataObject.fromJson<MessagingFriendInfo>(it) }, friends.mapNotNull { toParcelable<MessagingFriendInfo>(it) },
groups.map { SerializableDataObject.fromJson<MessagingGroupInfo>(it) } groups.mapNotNull { toParcelable<MessagingGroupInfo>(it) }
) )
} }

View File

@ -6,7 +6,6 @@ import me.rhunk.snapenhance.common.data.FriendStreaks
import me.rhunk.snapenhance.common.data.MessagingFriendInfo import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.data.MessagingRuleType import me.rhunk.snapenhance.common.data.MessagingRuleType
import me.rhunk.snapenhance.common.database.impl.FriendInfo
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper import me.rhunk.snapenhance.common.util.SQLiteDatabaseHelper
import me.rhunk.snapenhance.common.util.ktx.getInteger import me.rhunk.snapenhance.common.util.ktx.getInteger
@ -56,7 +55,7 @@ class ModDatabase(
"targetUuid VARCHAR" "targetUuid VARCHAR"
), ),
"streaks" to listOf( "streaks" to listOf(
"userId VARCHAR PRIMARY KEY", "id VARCHAR PRIMARY KEY",
"notify BOOLEAN", "notify BOOLEAN",
"expirationTimestamp BIGINT", "expirationTimestamp BIGINT",
"length INTEGER" "length INTEGER"
@ -78,10 +77,10 @@ class ModDatabase(
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
groups.add( groups.add(
MessagingGroupInfo( MessagingGroupInfo(
conversationId = cursor.getStringOrNull("conversationId")!!, conversationId = cursor.getStringOrNull("conversationId")!!,
name = cursor.getStringOrNull("name")!!, name = cursor.getStringOrNull("name")!!,
participantsCount = cursor.getInteger("participantsCount") participantsCount = cursor.getInteger("participantsCount")
) )
) )
} }
groups groups
@ -89,18 +88,25 @@ class ModDatabase(
} }
fun getFriends(descOrder: Boolean = false): List<MessagingFriendInfo> { fun getFriends(descOrder: Boolean = false): List<MessagingFriendInfo> {
return database.rawQuery("SELECT * FROM friends ORDER BY id ${if (descOrder) "DESC" else "ASC"}", null).use { cursor -> return database.rawQuery("SELECT * FROM friends LEFT OUTER JOIN streaks ON friends.userId = streaks.id ORDER BY id ${if (descOrder) "DESC" else "ASC"}", null).use { cursor ->
val friends = mutableListOf<MessagingFriendInfo>() val friends = mutableListOf<MessagingFriendInfo>()
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
runCatching { runCatching {
friends.add( friends.add(
MessagingFriendInfo( MessagingFriendInfo(
userId = cursor.getStringOrNull("userId")!!, userId = cursor.getStringOrNull("userId")!!,
displayName = cursor.getStringOrNull("displayName"), displayName = cursor.getStringOrNull("displayName"),
mutableUsername = cursor.getStringOrNull("mutableUsername")!!, mutableUsername = cursor.getStringOrNull("mutableUsername")!!,
bitmojiId = cursor.getStringOrNull("bitmojiId"), bitmojiId = cursor.getStringOrNull("bitmojiId"),
selfieId = cursor.getStringOrNull("selfieId") selfieId = cursor.getStringOrNull("selfieId"),
) streaks = cursor.getLongOrNull("expirationTimestamp")?.let {
FriendStreaks(
notify = cursor.getInteger("notify") == 1,
expirationTimestamp = it,
length = cursor.getInteger("length")
)
}
)
) )
}.onFailure { }.onFailure {
context.log.error("Failed to parse friend", it) context.log.error("Failed to parse friend", it)
@ -125,7 +131,7 @@ class ModDatabase(
} }
} }
fun syncFriend(friend: FriendInfo) { fun syncFriend(friend: MessagingFriendInfo) {
executeAsync { executeAsync {
try { try {
database.execSQL( database.execSQL(
@ -133,24 +139,22 @@ class ModDatabase(
arrayOf( arrayOf(
friend.userId, friend.userId,
friend.displayName, friend.displayName,
friend.usernameForSorting!!, friend.mutableUsername,
friend.bitmojiAvatarId, friend.bitmojiId,
friend.bitmojiSelfieId friend.selfieId
) )
) )
//sync streaks //sync streaks
if (friend.streakLength > 0) { friend.streaks?.takeIf { it.length > 0 }?.let {
val streaks = getFriendStreaks(friend.userId!!) val streaks = getFriendStreaks(friend.userId)
database.execSQL("INSERT OR REPLACE INTO streaks (userId, notify, expirationTimestamp, length) VALUES (?, ?, ?, ?)", arrayOf( database.execSQL("INSERT OR REPLACE INTO streaks (id, notify, expirationTimestamp, length) VALUES (?, ?, ?, ?)", arrayOf(
friend.userId, friend.userId,
streaks?.notify ?: true, streaks?.notify ?: true,
friend.streakExpirationTimestamp, it.expirationTimestamp,
friend.streakLength it.length
)) ))
} else { } ?: database.execSQL("DELETE FROM streaks WHERE id = ?", arrayOf(friend.userId))
database.execSQL("DELETE FROM streaks WHERE userId = ?", arrayOf(friend.userId))
}
} catch (e: Exception) { } catch (e: Exception) {
throw e throw e
} }
@ -190,14 +194,21 @@ class ModDatabase(
} }
fun getFriendInfo(userId: String): MessagingFriendInfo? { fun getFriendInfo(userId: String): MessagingFriendInfo? {
return database.rawQuery("SELECT * FROM friends WHERE userId = ?", arrayOf(userId)).use { cursor -> return database.rawQuery("SELECT * FROM friends LEFT OUTER JOIN streaks ON friends.userId = streaks.id WHERE userId = ?", arrayOf(userId)).use { cursor ->
if (!cursor.moveToFirst()) return@use null if (!cursor.moveToFirst()) return@use null
MessagingFriendInfo( MessagingFriendInfo(
userId = cursor.getStringOrNull("userId")!!, userId = cursor.getStringOrNull("userId")!!,
displayName = cursor.getStringOrNull("displayName"), displayName = cursor.getStringOrNull("displayName"),
mutableUsername = cursor.getStringOrNull("mutableUsername")!!, mutableUsername = cursor.getStringOrNull("mutableUsername")!!,
bitmojiId = cursor.getStringOrNull("bitmojiId"), bitmojiId = cursor.getStringOrNull("bitmojiId"),
selfieId = cursor.getStringOrNull("selfieId") selfieId = cursor.getStringOrNull("selfieId"),
streaks = cursor.getLongOrNull("expirationTimestamp")?.let {
FriendStreaks(
notify = cursor.getInteger("notify") == 1,
expirationTimestamp = it,
length = cursor.getInteger("length")
)
}
) )
} }
} }
@ -205,7 +216,7 @@ class ModDatabase(
fun deleteFriend(userId: String) { fun deleteFriend(userId: String) {
executeAsync { executeAsync {
database.execSQL("DELETE FROM friends WHERE userId = ?", arrayOf(userId)) database.execSQL("DELETE FROM friends WHERE userId = ?", arrayOf(userId))
database.execSQL("DELETE FROM streaks WHERE userId = ?", arrayOf(userId)) database.execSQL("DELETE FROM streaks WHERE id = ?", arrayOf(userId))
database.execSQL("DELETE FROM rules WHERE targetUuid = ?", arrayOf(userId)) database.execSQL("DELETE FROM rules WHERE targetUuid = ?", arrayOf(userId))
} }
} }
@ -229,10 +240,9 @@ class ModDatabase(
} }
fun getFriendStreaks(userId: String): FriendStreaks? { fun getFriendStreaks(userId: String): FriendStreaks? {
return database.rawQuery("SELECT * FROM streaks WHERE userId = ?", arrayOf(userId)).use { cursor -> return database.rawQuery("SELECT * FROM streaks WHERE id = ?", arrayOf(userId)).use { cursor ->
if (!cursor.moveToFirst()) return@use null if (!cursor.moveToFirst()) return@use null
FriendStreaks( FriendStreaks(
userId = cursor.getStringOrNull("userId")!!,
notify = cursor.getInteger("notify") == 1, notify = cursor.getInteger("notify") == 1,
expirationTimestamp = cursor.getLongOrNull("expirationTimestamp") ?: 0L, expirationTimestamp = cursor.getLongOrNull("expirationTimestamp") ?: 0L,
length = cursor.getInteger("length") length = cursor.getInteger("length")
@ -242,7 +252,7 @@ class ModDatabase(
fun setFriendStreaksNotify(userId: String, notify: Boolean) { fun setFriendStreaksNotify(userId: String, notify: Boolean) {
executeAsync { executeAsync {
database.execSQL("UPDATE streaks SET notify = ? WHERE userId = ?", arrayOf( database.execSQL("UPDATE streaks SET notify = ? WHERE id = ?", arrayOf(
if (notify) 1 else 0, if (notify) 1 else 0,
userId userId
)) ))

View File

@ -26,7 +26,9 @@ import androidx.compose.ui.window.Dialog
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navigation import androidx.navigation.navigation
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.R import me.rhunk.snapenhance.R
import me.rhunk.snapenhance.common.data.MessagingFriendInfo import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo
@ -214,8 +216,13 @@ class SocialSection : Section() {
SocialScope.FRIEND -> { SocialScope.FRIEND -> {
val friend = friendList[index] val friend = friendList[index]
val streaks = var streaks by remember { mutableStateOf(friend.streaks) }
remember { context.modDatabase.getFriendStreaks(friend.userId) }
LaunchedEffect(friend.userId) {
withContext(Dispatchers.IO) {
streaks = context.modDatabase.getFriendStreaks(friend.userId)
}
}
BitmojiImage( BitmojiImage(
context = context, context = context,
@ -244,7 +251,7 @@ class SocialSection : Section() {
) )
} }
Row(verticalAlignment = Alignment.CenterVertically) { Row(verticalAlignment = Alignment.CenterVertically) {
if (streaks != null && streaks.notify) { streaks?.takeIf { it.notify }?.let { streaks ->
Icon( Icon(
imageVector = ImageVector.vectorResource(id = R.drawable.streak_icon), imageVector = ImageVector.vectorResource(id = R.drawable.streak_icon),
contentDescription = null, contentDescription = null,
@ -256,7 +263,7 @@ class SocialSection : Section() {
Text( Text(
text = context.translation.format( text = context.translation.format(
"manager.sections.social.streaks_expiration_short", "manager.sections.social.streaks_expiration_short",
"hours" to ((streaks.expirationTimestamp - System.currentTimeMillis()) / 3600000).toInt() "hours" to (((streaks.expirationTimestamp - System.currentTimeMillis()) / 3600000).toInt().takeIf { it > 0 } ?: 0)
.toString() .toString()
), ),
maxLines = 1, maxLines = 1,

View File

@ -1,6 +1,7 @@
plugins { plugins {
alias(libs.plugins.androidLibrary) alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinAndroid) alias(libs.plugins.kotlinAndroid)
id("kotlin-parcelize")
} }
android { android {

View File

@ -13,7 +13,7 @@ interface BridgeInterface {
/** /**
* broadcast a log message * broadcast a log message
*/ */
void broadcastLog(String tag, String level, String message); oneway void broadcastLog(String tag, String level, String message);
/** /**
* Execute a file operation * Execute a file operation
@ -36,7 +36,7 @@ interface BridgeInterface {
/** /**
* Enqueue a download * Enqueue a download
*/ */
void enqueueDownload(in Intent intent, DownloadCallback callback); oneway void enqueueDownload(in Intent intent, DownloadCallback callback);
/** /**
* Get rules for a given user or conversation * Get rules for a given user or conversation
@ -56,7 +56,7 @@ interface BridgeInterface {
* *
* @param type rule type (MessagingRuleType) * @param type rule type (MessagingRuleType)
*/ */
void setRule(String uuid, String type, boolean state); oneway void setRule(String uuid, String type, boolean state);
/** /**
* Sync groups and friends * Sync groups and friends
@ -66,12 +66,12 @@ interface BridgeInterface {
/** /**
* Trigger sync for an id * Trigger sync for an id
*/ */
void triggerSync(String scope, String id); oneway void triggerSync(String scope, String id);
/** /**
* Pass all groups and friends to be able to add them to the database * Pass all groups and friends to be able to add them to the database
* @param groups list of groups (MessagingGroupInfo as json string) * @param groups list of groups (MessagingGroupInfo as parcelable)
* @param friends list of friends (MessagingFriendInfo as json string) * @param friends list of friends (MessagingFriendInfo as parcelable)
*/ */
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends); oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
@ -81,11 +81,11 @@ interface BridgeInterface {
MessageLoggerInterface getMessageLogger(); MessageLoggerInterface getMessageLogger();
void registerMessagingBridge(MessagingBridge bridge); oneway void registerMessagingBridge(MessagingBridge bridge);
void openSettingsOverlay(); oneway void openSettingsOverlay();
void closeSettingsOverlay(); oneway void closeSettingsOverlay();
void registerConfigStateListener(in ConfigStateListener listener); oneway void registerConfigStateListener(in ConfigStateListener listener);
} }

View File

@ -8,11 +8,11 @@ interface IScripting {
@nullable String getScriptContent(String path); @nullable String getScriptContent(String path);
void registerIPCListener(String channel, String eventName, IPCListener listener); oneway void registerIPCListener(String channel, String eventName, IPCListener listener);
void sendIPCMessage(String channel, String eventName, in String[] args); oneway void sendIPCMessage(String channel, String eventName, in String[] args);
@nullable String configTransaction(String module, String action, @nullable String key, @nullable String value, boolean save); @nullable String configTransaction(String module, String action, @nullable String key, @nullable String value, boolean save);
void registerAutoReloadListener(in AutoReloadListener listener); oneway void registerAutoReloadListener(in AutoReloadListener listener);
} }

View File

@ -33,7 +33,7 @@ class ModConfig(
private fun load() { private fun load() {
root = createRootConfig() root = createRootConfig()
wasPresent = file.isFileExists() wasPresent = file.isFileExists()
if (!file.isFileExists()) { if (!wasPresent) {
writeConfig() writeConfig()
return return
} }

View File

@ -1,7 +1,8 @@
package me.rhunk.snapenhance.common.data package me.rhunk.snapenhance.common.data
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import me.rhunk.snapenhance.common.config.FeatureNotice import me.rhunk.snapenhance.common.config.FeatureNotice
import me.rhunk.snapenhance.common.util.SerializableDataObject
import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.hours
@ -52,12 +53,12 @@ enum class MessagingRuleType(
} }
} }
@Parcelize
data class FriendStreaks( data class FriendStreaks(
val userId: String, val notify: Boolean = true,
val notify: Boolean,
val expirationTimestamp: Long, val expirationTimestamp: Long,
val length: Int val length: Int
) : SerializableDataObject() { ): Parcelable {
fun hoursLeft() = (expirationTimestamp - System.currentTimeMillis()) / 1000 / 60 / 60 fun hoursLeft() = (expirationTimestamp - System.currentTimeMillis()) / 1000 / 60 / 60
fun isAboutToExpire(expireHours: Int) = (expirationTimestamp - System.currentTimeMillis()).let { fun isAboutToExpire(expireHours: Int) = (expirationTimestamp - System.currentTimeMillis()).let {
@ -65,20 +66,22 @@ data class FriendStreaks(
} }
} }
@Parcelize
data class MessagingGroupInfo( data class MessagingGroupInfo(
val conversationId: String, val conversationId: String,
val name: String, val name: String,
val participantsCount: Int val participantsCount: Int
) : SerializableDataObject() ): Parcelable
@Parcelize
data class MessagingFriendInfo( data class MessagingFriendInfo(
val userId: String, val userId: String,
val displayName: String?, val displayName: String?,
val mutableUsername: String, val mutableUsername: String,
val bitmojiId: String?, val bitmojiId: String?,
val selfieId: String? val selfieId: String?,
) : SerializableDataObject() var streaks: FriendStreaks?,
): Parcelable
class StoryData( class StoryData(
val url: String, val url: String,
@ -86,4 +89,4 @@ class StoryData(
val createdAt: Long, val createdAt: Long,
val key: ByteArray?, val key: ByteArray?,
val iv: ByteArray? val iv: ByteArray?
) : SerializableDataObject() )

View File

@ -3,7 +3,6 @@ package me.rhunk.snapenhance.common.database.impl
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.Cursor import android.database.Cursor
import me.rhunk.snapenhance.common.database.DatabaseObject import me.rhunk.snapenhance.common.database.DatabaseObject
import me.rhunk.snapenhance.common.util.SerializableDataObject
import me.rhunk.snapenhance.common.util.ktx.getInteger import me.rhunk.snapenhance.common.util.ktx.getInteger
import me.rhunk.snapenhance.common.util.ktx.getLong import me.rhunk.snapenhance.common.util.ktx.getLong
import me.rhunk.snapenhance.common.util.ktx.getStringOrNull import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
@ -33,7 +32,7 @@ data class FriendInfo(
var usernameForSorting: String? = null, var usernameForSorting: String? = null,
var friendLinkType: Int = 0, var friendLinkType: Int = 0,
var postViewEmoji: String? = null, var postViewEmoji: String? = null,
) : DatabaseObject, SerializableDataObject() { ) : DatabaseObject {
val mutableUsername get() = username?.split("|")?.last() val mutableUsername get() = username?.split("|")?.last()
val firstCreatedUsername get() = username?.split("|")?.first() val firstCreatedUsername get() = username?.split("|")?.first()

View File

@ -0,0 +1,33 @@
package me.rhunk.snapenhance.common.util
import android.os.Parcelable
import kotlinx.parcelize.parcelableCreator
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
@OptIn(ExperimentalEncodingApi::class)
fun Parcelable.toSerialized(): String? {
val parcel = android.os.Parcel.obtain()
return try {
writeToParcel(parcel, 0)
parcel.marshall()?.let {
Base64.encode(it)
}
} finally {
parcel.recycle()
}
}
@OptIn(ExperimentalEncodingApi::class)
inline fun <reified T : Parcelable> toParcelable(serialized: String): T? {
val parcel = android.os.Parcel.obtain()
return try {
Base64.decode(serialized).let {
parcel.unmarshall(it, 0, it.size)
}
parcel.setDataPosition(0)
parcelableCreator<T>().createFromParcel(parcel)
} finally {
parcel.recycle()
}
}

View File

@ -1,22 +0,0 @@
package me.rhunk.snapenhance.common.util
import com.google.gson.Gson
import com.google.gson.GsonBuilder
open class SerializableDataObject {
companion object {
val gson: Gson = GsonBuilder().create()
inline fun <reified T : SerializableDataObject> fromJson(json: String): T {
return gson.fromJson(json, T::class.java)
}
inline fun <reified T : SerializableDataObject> fromJson(json: String, type: Class<T>): T {
return gson.fromJson(json, type)
}
}
fun toJson(): String {
return gson.toJson(this)
}
}

View File

@ -13,8 +13,10 @@ import me.rhunk.snapenhance.bridge.SyncCallback
import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.Constants
import me.rhunk.snapenhance.common.ReceiversConfig import me.rhunk.snapenhance.common.ReceiversConfig
import me.rhunk.snapenhance.common.action.EnumAction import me.rhunk.snapenhance.common.action.EnumAction
import me.rhunk.snapenhance.common.data.FriendStreaks
import me.rhunk.snapenhance.common.data.MessagingFriendInfo import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.util.toSerialized
import me.rhunk.snapenhance.core.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.core.bridge.loadFromBridge import me.rhunk.snapenhance.core.bridge.loadFromBridge
import me.rhunk.snapenhance.core.data.SnapClassCache import me.rhunk.snapenhance.core.data.SnapClassCache
@ -151,7 +153,6 @@ class SnapEnhance {
features.init() features.init()
scriptRuntime.connect(bridgeClient.getScriptingInterface()) scriptRuntime.connect(bridgeClient.getScriptingInterface())
scriptRuntime.eachModule { callFunction("module.onSnapApplicationLoad", androidContext) } scriptRuntime.eachModule { callFunction("module.onSnapApplicationLoad", androidContext) }
syncRemote()
} }
} }
@ -195,7 +196,7 @@ class SnapEnhance {
} }
} }
appContext.apply { appContext.executeAsync {
bridgeClient.registerConfigStateListener(object: ConfigStateListener.Stub() { bridgeClient.registerConfigStateListener(object: ConfigStateListener.Stub() {
override fun onConfigChanged() { override fun onConfigChanged() {
log.verbose("onConfigChanged") log.verbose("onConfigChanged")
@ -227,7 +228,21 @@ class SnapEnhance {
appContext.executeAsync { appContext.executeAsync {
bridgeClient.sync(object : SyncCallback.Stub() { bridgeClient.sync(object : SyncCallback.Stub() {
override fun syncFriend(uuid: String): String? { override fun syncFriend(uuid: String): String? {
return database.getFriendInfo(uuid)?.toJson() return database.getFriendInfo(uuid)?.let {
MessagingFriendInfo(
userId = it.userId!!,
displayName = it.displayName,
mutableUsername = it.mutableUsername!!,
bitmojiId = it.bitmojiAvatarId,
selfieId = it.bitmojiSelfieId,
streaks = if (it.streakLength > 0) {
FriendStreaks(
expirationTimestamp = it.streakExpirationTimestamp,
length = it.streakLength
)
} else null
).toSerialized()
}
} }
override fun syncGroup(uuid: String): String? { override fun syncGroup(uuid: String): String? {
@ -236,7 +251,7 @@ class SnapEnhance {
it.key!!, it.key!!,
it.feedDisplayName!!, it.feedDisplayName!!,
it.participantsSize it.participantsSize
).toJson() ).toSerialized()
} }
} }
}) })
@ -260,14 +275,12 @@ class SnapEnhance {
it.friendDisplayName, it.friendDisplayName,
it.friendDisplayUsername!!.split("|")[1], it.friendDisplayUsername!!.split("|")[1],
it.bitmojiAvatarId, it.bitmojiAvatarId,
it.bitmojiSelfieId it.bitmojiSelfieId,
streaks = null
) )
} }
bridgeClient.passGroupsAndFriends( bridgeClient.passGroupsAndFriends(groups, friends)
groups.map { it.toJson() },
friends.map { it.toJson() }
)
} }
} }
} }

View File

@ -23,8 +23,11 @@ import me.rhunk.snapenhance.common.bridge.FileLoaderWrapper
import me.rhunk.snapenhance.common.bridge.types.BridgeFileType import me.rhunk.snapenhance.common.bridge.types.BridgeFileType
import me.rhunk.snapenhance.common.bridge.types.FileActionType import me.rhunk.snapenhance.common.bridge.types.FileActionType
import me.rhunk.snapenhance.common.bridge.types.LocalePair import me.rhunk.snapenhance.common.bridge.types.LocalePair
import me.rhunk.snapenhance.common.data.MessagingFriendInfo
import me.rhunk.snapenhance.common.data.MessagingGroupInfo
import me.rhunk.snapenhance.common.data.MessagingRuleType import me.rhunk.snapenhance.common.data.MessagingRuleType
import me.rhunk.snapenhance.common.data.SocialScope import me.rhunk.snapenhance.common.data.SocialScope
import me.rhunk.snapenhance.common.util.toSerialized
import me.rhunk.snapenhance.core.ModContext import me.rhunk.snapenhance.core.ModContext
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -136,7 +139,10 @@ class BridgeClient(
fun triggerSync(scope: SocialScope, id: String) = service.triggerSync(scope.key, id) fun triggerSync(scope: SocialScope, id: String) = service.triggerSync(scope.key, id)
fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends) fun passGroupsAndFriends(groups: List<MessagingGroupInfo>, friends: List<MessagingFriendInfo>) = service.passGroupsAndFriends(
groups.mapNotNull { it.toSerialized() },
friends.mapNotNull { it.toSerialized() }
)
fun getRules(targetUuid: String): List<MessagingRuleType> { fun getRules(targetUuid: String): List<MessagingRuleType> {
return service.getRules(targetUuid).mapNotNull { MessagingRuleType.getByName(it) } return service.getRules(targetUuid).mapNotNull { MessagingRuleType.getByName(it) }

View File

@ -15,12 +15,12 @@ class ActionManager(
private val actions by lazy { private val actions by lazy {
mapOf( mapOf(
EnumAction.CLEAN_CACHE to CleanCache::class, EnumAction.CLEAN_CACHE to CleanCache(),
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages::class, EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages(),
EnumAction.BULK_MESSAGING_ACTION to BulkMessagingAction::class, EnumAction.BULK_MESSAGING_ACTION to BulkMessagingAction(),
EnumAction.EXPORT_MEMORIES to ExportMemories::class, EnumAction.EXPORT_MEMORIES to ExportMemories(),
).map { ).map {
it.key to it.value.java.getConstructor().newInstance().apply { it.key to it.value.apply {
this.context = modContext this.context = modContext
} }
}.toMap().toMutableMap() }.toMap().toMutableMap()

View File

@ -8,17 +8,15 @@ import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.features.FeatureLoadParams import me.rhunk.snapenhance.core.features.FeatureLoadParams
import me.rhunk.snapenhance.core.features.MessagingRuleFeature import me.rhunk.snapenhance.core.features.MessagingRuleFeature
import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride import me.rhunk.snapenhance.core.features.impl.ConfigurationOverride
import me.rhunk.snapenhance.core.features.impl.MixerStories
import me.rhunk.snapenhance.core.features.impl.OperaViewerParamsOverride import me.rhunk.snapenhance.core.features.impl.OperaViewerParamsOverride
import me.rhunk.snapenhance.core.features.impl.ScopeSync import me.rhunk.snapenhance.core.features.impl.ScopeSync
import me.rhunk.snapenhance.core.features.impl.MixerStories
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.features.impl.downloader.ProfilePictureDownloader import me.rhunk.snapenhance.core.features.impl.downloader.ProfilePictureDownloader
import me.rhunk.snapenhance.core.features.impl.experiments.* import me.rhunk.snapenhance.core.features.impl.experiments.*
import me.rhunk.snapenhance.core.features.impl.global.* import me.rhunk.snapenhance.core.features.impl.global.*
import me.rhunk.snapenhance.core.features.impl.messaging.* import me.rhunk.snapenhance.core.features.impl.messaging.*
import me.rhunk.snapenhance.core.features.impl.spying.HalfSwipeNotifier import me.rhunk.snapenhance.core.features.impl.spying.*
import me.rhunk.snapenhance.core.features.impl.spying.MessageLogger
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
import me.rhunk.snapenhance.core.features.impl.tweaks.BypassScreenshotDetection import me.rhunk.snapenhance.core.features.impl.tweaks.BypassScreenshotDetection
import me.rhunk.snapenhance.core.features.impl.tweaks.CameraTweaks import me.rhunk.snapenhance.core.features.impl.tweaks.CameraTweaks
import me.rhunk.snapenhance.core.features.impl.tweaks.PreventMessageListAutoScroll import me.rhunk.snapenhance.core.features.impl.tweaks.PreventMessageListAutoScroll
@ -33,23 +31,19 @@ import kotlin.system.measureTimeMillis
class FeatureManager( class FeatureManager(
private val context: ModContext private val context: ModContext
) : Manager { ) : Manager {
private val features = mutableListOf<Feature>() private val features = mutableMapOf<KClass<out Feature>, Feature>()
private fun register(vararg featureClasses: KClass<out Feature>) { private fun register(vararg featureList: Feature) {
runBlocking { runBlocking {
featureClasses.forEach { clazz -> featureList.forEach { feature ->
launch(Dispatchers.IO) { launch(Dispatchers.IO) {
runCatching { runCatching {
clazz.java.constructors.first().newInstance() feature.context = context
.let { it as Feature } synchronized(features) {
.also { features[feature::class] = feature
it.context = context }
synchronized(features) {
features.add(it)
}
}
}.onFailure { }.onFailure {
CoreLogger.xposedLog("Failed to register feature ${clazz.simpleName}", it) CoreLogger.xposedLog("Failed to register feature ${feature.featureKey}", it)
} }
} }
} }
@ -58,91 +52,92 @@ class FeatureManager(
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
fun <T : Feature> get(featureClass: KClass<T>): T? { fun <T : Feature> get(featureClass: KClass<T>): T? {
return features.find { it::class == featureClass } as? T return features[featureClass] as? T
} }
fun getRuleFeatures() = features.filterIsInstance<MessagingRuleFeature>().sortedBy { it.ruleType.ordinal } fun getRuleFeatures() = features.values.filterIsInstance<MessagingRuleFeature>().sortedBy { it.ruleType.ordinal }
override fun init() { override fun init() {
register( register(
EndToEndEncryption::class, EndToEndEncryption(),
ScopeSync::class, ScopeSync(),
PreventMessageListAutoScroll::class, PreventMessageListAutoScroll(),
Messaging::class, Messaging(),
MediaDownloader::class, MediaDownloader(),
StealthMode::class, StealthMode(),
MenuViewInjector::class, MenuViewInjector(),
PreventReadReceipts::class, PreventReadReceipts(),
MessageLogger::class, MessageLogger(),
ConvertMessageLocally::class, ConvertMessageLocally(),
SnapchatPlus::class, SnapchatPlus(),
DisableMetrics::class, DisableMetrics(),
PreventMessageSending::class, PreventMessageSending(),
Notifications::class, Notifications(),
AutoSave::class, AutoSave(),
UITweaks::class, UITweaks(),
ConfigurationOverride::class, ConfigurationOverride(),
UnsaveableMessages::class, UnsaveableMessages(),
SendOverride::class, SendOverride(),
UnlimitedSnapViewTime::class, UnlimitedSnapViewTime(),
BypassVideoLengthRestriction::class, BypassVideoLengthRestriction(),
MediaQualityLevelOverride::class, MediaQualityLevelOverride(),
MeoPasscodeBypass::class, MeoPasscodeBypass(),
AppPasscode::class, AppPasscode(),
LocationSpoofer::class, LocationSpoofer(),
CameraTweaks::class, CameraTweaks(),
InfiniteStoryBoost::class, InfiniteStoryBoost(),
AmoledDarkMode::class, AmoledDarkMode(),
PinConversations::class, PinConversations(),
UnlimitedMultiSnap::class, UnlimitedMultiSnap(),
DeviceSpooferHook::class, DeviceSpooferHook(),
ClientBootstrapOverride::class, ClientBootstrapOverride(),
GooglePlayServicesDialogs::class, GooglePlayServicesDialogs(),
NoFriendScoreDelay::class, NoFriendScoreDelay(),
ProfilePictureDownloader::class, ProfilePictureDownloader(),
AddFriendSourceSpoof::class, AddFriendSourceSpoof(),
DisableReplayInFF::class, DisableReplayInFF(),
OldBitmojiSelfie::class, OldBitmojiSelfie(),
FriendFeedMessagePreview::class, FriendFeedMessagePreview(),
HideStreakRestore::class, HideStreakRestore(),
HideFriendFeedEntry::class, HideFriendFeedEntry(),
HideQuickAddFriendFeed::class, HideQuickAddFriendFeed(),
CallStartConfirmation::class, CallStartConfirmation(),
SnapPreview::class, SnapPreview(),
InstantDelete::class, InstantDelete(),
BypassScreenshotDetection::class, BypassScreenshotDetection(),
HalfSwipeNotifier::class, HalfSwipeNotifier(),
DisableConfirmationDialogs::class, DisableConfirmationDialogs(),
MixerStories::class, MixerStories(),
DisableComposerModules::class, DisableComposerModules(),
FideliusIndicator::class, FideliusIndicator(),
EditTextOverride::class, EditTextOverride(),
PreventForcedLogout::class, PreventForcedLogout(),
SuspendLocationUpdates::class, SuspendLocationUpdates(),
ConversationToolbox::class, ConversationToolbox(),
SpotlightCommentsUsername::class, SpotlightCommentsUsername(),
OperaViewerParamsOverride::class, OperaViewerParamsOverride(),
) )
initializeFeatures() initializeFeatures()
} }
private inline fun tryInit(feature: Feature, crossinline block: () -> Unit) {
runCatching {
block()
}.onFailure {
context.log.error("Failed to init feature ${feature.featureKey}", it)
context.longToast("Failed to init feature ${feature.featureKey}! Check logcat for more details.")
}
}
private fun initFeatures( private fun initFeatures(
syncParam: Int, syncParam: Int,
asyncParam: Int, asyncParam: Int,
syncAction: (Feature) -> Unit, syncAction: (Feature) -> Unit,
asyncAction: (Feature) -> Unit asyncAction: (Feature) -> Unit
) { ) {
fun tryInit(feature: Feature, block: () -> Unit) {
runCatching {
block()
}.onFailure {
context.log.error("Failed to init feature ${feature.featureKey}", it)
context.longToast("Failed to init feature ${feature.featureKey}! Check logcat for more details.")
}
}
features.toList().forEach { feature -> features.values.toList().forEach { feature ->
if (feature.loadParams and syncParam != 0) { if (feature.loadParams and syncParam != 0) {
tryInit(feature) { tryInit(feature) {
syncAction(feature) syncAction(feature)