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 {
alias(libs.plugins.androidApplication)
alias(libs.plugins.kotlinAndroid)
id("kotlin-parcelize")
}
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.MessagingGroupInfo
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.util.SerializableDataObject
import me.rhunk.snapenhance.common.util.toParcelable
import me.rhunk.snapenhance.download.DownloadProcessor
import kotlin.system.measureTimeMillis
@ -61,12 +60,12 @@ class BridgeService : Service() {
when (scope) {
SocialScope.FRIEND -> {
SerializableDataObject.fromJson<FriendInfo>(syncedObject).let {
toParcelable<MessagingFriendInfo>(syncedObject)?.let {
modDatabase.syncFriend(it)
}
}
SocialScope.GROUP -> {
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedObject).let {
toParcelable<MessagingGroupInfo>(syncedObject)?.let {
modDatabase.syncGroupInfo(it)
}
}
@ -169,8 +168,8 @@ class BridgeService : Service() {
) {
remoteSideContext.log.verbose("Received ${groups.size} groups and ${friends.size} friends")
remoteSideContext.modDatabase.receiveMessagingDataCallback(
friends.map { SerializableDataObject.fromJson<MessagingFriendInfo>(it) },
groups.map { SerializableDataObject.fromJson<MessagingGroupInfo>(it) }
friends.mapNotNull { toParcelable<MessagingFriendInfo>(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.MessagingGroupInfo
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.util.SQLiteDatabaseHelper
import me.rhunk.snapenhance.common.util.ktx.getInteger
@ -56,7 +55,7 @@ class ModDatabase(
"targetUuid VARCHAR"
),
"streaks" to listOf(
"userId VARCHAR PRIMARY KEY",
"id VARCHAR PRIMARY KEY",
"notify BOOLEAN",
"expirationTimestamp BIGINT",
"length INTEGER"
@ -89,7 +88,7 @@ class ModDatabase(
}
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>()
while (cursor.moveToNext()) {
runCatching {
@ -99,7 +98,14 @@ class ModDatabase(
displayName = cursor.getStringOrNull("displayName"),
mutableUsername = cursor.getStringOrNull("mutableUsername")!!,
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 {
@ -125,7 +131,7 @@ class ModDatabase(
}
}
fun syncFriend(friend: FriendInfo) {
fun syncFriend(friend: MessagingFriendInfo) {
executeAsync {
try {
database.execSQL(
@ -133,24 +139,22 @@ class ModDatabase(
arrayOf(
friend.userId,
friend.displayName,
friend.usernameForSorting!!,
friend.bitmojiAvatarId,
friend.bitmojiSelfieId
friend.mutableUsername,
friend.bitmojiId,
friend.selfieId
)
)
//sync streaks
if (friend.streakLength > 0) {
val streaks = getFriendStreaks(friend.userId!!)
friend.streaks?.takeIf { it.length > 0 }?.let {
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,
streaks?.notify ?: true,
friend.streakExpirationTimestamp,
friend.streakLength
it.expirationTimestamp,
it.length
))
} else {
database.execSQL("DELETE FROM streaks WHERE userId = ?", arrayOf(friend.userId))
}
} ?: database.execSQL("DELETE FROM streaks WHERE id = ?", arrayOf(friend.userId))
} catch (e: Exception) {
throw e
}
@ -190,14 +194,21 @@ class ModDatabase(
}
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
MessagingFriendInfo(
userId = cursor.getStringOrNull("userId")!!,
displayName = cursor.getStringOrNull("displayName"),
mutableUsername = cursor.getStringOrNull("mutableUsername")!!,
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) {
executeAsync {
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))
}
}
@ -229,10 +240,9 @@ class ModDatabase(
}
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
FriendStreaks(
userId = cursor.getStringOrNull("userId")!!,
notify = cursor.getInteger("notify") == 1,
expirationTimestamp = cursor.getLongOrNull("expirationTimestamp") ?: 0L,
length = cursor.getInteger("length")
@ -242,7 +252,7 @@ class ModDatabase(
fun setFriendStreaksNotify(userId: String, notify: Boolean) {
executeAsync {
database.execSQL("UPDATE streaks SET notify = ? WHERE userId = ?", arrayOf(
database.execSQL("UPDATE streaks SET notify = ? WHERE id = ?", arrayOf(
if (notify) 1 else 0,
userId
))

View File

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

View File

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

View File

@ -13,7 +13,7 @@ interface BridgeInterface {
/**
* 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
@ -36,7 +36,7 @@ interface BridgeInterface {
/**
* 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
@ -56,7 +56,7 @@ interface BridgeInterface {
*
* @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
@ -66,12 +66,12 @@ interface BridgeInterface {
/**
* 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
* @param groups list of groups (MessagingGroupInfo as json string)
* @param friends list of friends (MessagingFriendInfo as json string)
* @param groups list of groups (MessagingGroupInfo as parcelable)
* @param friends list of friends (MessagingFriendInfo as parcelable)
*/
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
@ -81,11 +81,11 @@ interface BridgeInterface {
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);
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);
void registerAutoReloadListener(in AutoReloadListener listener);
oneway void registerAutoReloadListener(in AutoReloadListener listener);
}

View File

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

View File

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

View File

@ -3,7 +3,6 @@ package me.rhunk.snapenhance.common.database.impl
import android.annotation.SuppressLint
import android.database.Cursor
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.getLong
import me.rhunk.snapenhance.common.util.ktx.getStringOrNull
@ -33,7 +32,7 @@ data class FriendInfo(
var usernameForSorting: String? = null,
var friendLinkType: Int = 0,
var postViewEmoji: String? = null,
) : DatabaseObject, SerializableDataObject() {
) : DatabaseObject {
val mutableUsername get() = username?.split("|")?.last()
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.ReceiversConfig
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.MessagingGroupInfo
import me.rhunk.snapenhance.common.util.toSerialized
import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.core.bridge.loadFromBridge
import me.rhunk.snapenhance.core.data.SnapClassCache
@ -151,7 +153,6 @@ class SnapEnhance {
features.init()
scriptRuntime.connect(bridgeClient.getScriptingInterface())
scriptRuntime.eachModule { callFunction("module.onSnapApplicationLoad", androidContext) }
syncRemote()
}
}
@ -195,7 +196,7 @@ class SnapEnhance {
}
}
appContext.apply {
appContext.executeAsync {
bridgeClient.registerConfigStateListener(object: ConfigStateListener.Stub() {
override fun onConfigChanged() {
log.verbose("onConfigChanged")
@ -227,7 +228,21 @@ class SnapEnhance {
appContext.executeAsync {
bridgeClient.sync(object : SyncCallback.Stub() {
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? {
@ -236,7 +251,7 @@ class SnapEnhance {
it.key!!,
it.feedDisplayName!!,
it.participantsSize
).toJson()
).toSerialized()
}
}
})
@ -260,14 +275,12 @@ class SnapEnhance {
it.friendDisplayName,
it.friendDisplayUsername!!.split("|")[1],
it.bitmojiAvatarId,
it.bitmojiSelfieId
it.bitmojiSelfieId,
streaks = null
)
}
bridgeClient.passGroupsAndFriends(
groups.map { it.toJson() },
friends.map { it.toJson() }
)
bridgeClient.passGroupsAndFriends(groups, friends)
}
}
}

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.FileActionType
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.SocialScope
import me.rhunk.snapenhance.common.util.toSerialized
import me.rhunk.snapenhance.core.ModContext
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
@ -136,7 +139,10 @@ class BridgeClient(
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> {
return service.getRules(targetUuid).mapNotNull { MessagingRuleType.getByName(it) }

View File

@ -15,12 +15,12 @@ class ActionManager(
private val actions by lazy {
mapOf(
EnumAction.CLEAN_CACHE to CleanCache::class,
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages::class,
EnumAction.BULK_MESSAGING_ACTION to BulkMessagingAction::class,
EnumAction.EXPORT_MEMORIES to ExportMemories::class,
EnumAction.CLEAN_CACHE to CleanCache(),
EnumAction.EXPORT_CHAT_MESSAGES to ExportChatMessages(),
EnumAction.BULK_MESSAGING_ACTION to BulkMessagingAction(),
EnumAction.EXPORT_MEMORIES to ExportMemories(),
).map {
it.key to it.value.java.getConstructor().newInstance().apply {
it.key to it.value.apply {
this.context = modContext
}
}.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.MessagingRuleFeature
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.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.ProfilePictureDownloader
import me.rhunk.snapenhance.core.features.impl.experiments.*
import me.rhunk.snapenhance.core.features.impl.global.*
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.MessageLogger
import me.rhunk.snapenhance.core.features.impl.spying.StealthMode
import me.rhunk.snapenhance.core.features.impl.spying.*
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.PreventMessageListAutoScroll
@ -33,23 +31,19 @@ import kotlin.system.measureTimeMillis
class FeatureManager(
private val context: ModContext
) : 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 {
featureClasses.forEach { clazz ->
featureList.forEach { feature ->
launch(Dispatchers.IO) {
runCatching {
clazz.java.constructors.first().newInstance()
.let { it as Feature }
.also {
it.context = context
feature.context = context
synchronized(features) {
features.add(it)
}
features[feature::class] = feature
}
}.onFailure {
CoreLogger.xposedLog("Failed to register feature ${clazz.simpleName}", it)
CoreLogger.xposedLog("Failed to register feature ${feature.featureKey}", it)
}
}
}
@ -58,82 +52,76 @@ class FeatureManager(
@Suppress("UNCHECKED_CAST")
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() {
register(
EndToEndEncryption::class,
ScopeSync::class,
PreventMessageListAutoScroll::class,
Messaging::class,
MediaDownloader::class,
StealthMode::class,
MenuViewInjector::class,
PreventReadReceipts::class,
MessageLogger::class,
ConvertMessageLocally::class,
SnapchatPlus::class,
DisableMetrics::class,
PreventMessageSending::class,
Notifications::class,
AutoSave::class,
UITweaks::class,
ConfigurationOverride::class,
UnsaveableMessages::class,
SendOverride::class,
UnlimitedSnapViewTime::class,
BypassVideoLengthRestriction::class,
MediaQualityLevelOverride::class,
MeoPasscodeBypass::class,
AppPasscode::class,
LocationSpoofer::class,
CameraTweaks::class,
InfiniteStoryBoost::class,
AmoledDarkMode::class,
PinConversations::class,
UnlimitedMultiSnap::class,
DeviceSpooferHook::class,
ClientBootstrapOverride::class,
GooglePlayServicesDialogs::class,
NoFriendScoreDelay::class,
ProfilePictureDownloader::class,
AddFriendSourceSpoof::class,
DisableReplayInFF::class,
OldBitmojiSelfie::class,
FriendFeedMessagePreview::class,
HideStreakRestore::class,
HideFriendFeedEntry::class,
HideQuickAddFriendFeed::class,
CallStartConfirmation::class,
SnapPreview::class,
InstantDelete::class,
BypassScreenshotDetection::class,
HalfSwipeNotifier::class,
DisableConfirmationDialogs::class,
MixerStories::class,
DisableComposerModules::class,
FideliusIndicator::class,
EditTextOverride::class,
PreventForcedLogout::class,
SuspendLocationUpdates::class,
ConversationToolbox::class,
SpotlightCommentsUsername::class,
OperaViewerParamsOverride::class,
EndToEndEncryption(),
ScopeSync(),
PreventMessageListAutoScroll(),
Messaging(),
MediaDownloader(),
StealthMode(),
MenuViewInjector(),
PreventReadReceipts(),
MessageLogger(),
ConvertMessageLocally(),
SnapchatPlus(),
DisableMetrics(),
PreventMessageSending(),
Notifications(),
AutoSave(),
UITweaks(),
ConfigurationOverride(),
UnsaveableMessages(),
SendOverride(),
UnlimitedSnapViewTime(),
BypassVideoLengthRestriction(),
MediaQualityLevelOverride(),
MeoPasscodeBypass(),
AppPasscode(),
LocationSpoofer(),
CameraTweaks(),
InfiniteStoryBoost(),
AmoledDarkMode(),
PinConversations(),
UnlimitedMultiSnap(),
DeviceSpooferHook(),
ClientBootstrapOverride(),
GooglePlayServicesDialogs(),
NoFriendScoreDelay(),
ProfilePictureDownloader(),
AddFriendSourceSpoof(),
DisableReplayInFF(),
OldBitmojiSelfie(),
FriendFeedMessagePreview(),
HideStreakRestore(),
HideFriendFeedEntry(),
HideQuickAddFriendFeed(),
CallStartConfirmation(),
SnapPreview(),
InstantDelete(),
BypassScreenshotDetection(),
HalfSwipeNotifier(),
DisableConfirmationDialogs(),
MixerStories(),
DisableComposerModules(),
FideliusIndicator(),
EditTextOverride(),
PreventForcedLogout(),
SuspendLocationUpdates(),
ConversationToolbox(),
SpotlightCommentsUsername(),
OperaViewerParamsOverride(),
)
initializeFeatures()
}
private fun initFeatures(
syncParam: Int,
asyncParam: Int,
syncAction: (Feature) -> Unit,
asyncAction: (Feature) -> Unit
) {
fun tryInit(feature: Feature, block: () -> Unit) {
private inline fun tryInit(feature: Feature, crossinline block: () -> Unit) {
runCatching {
block()
}.onFailure {
@ -142,7 +130,14 @@ class FeatureManager(
}
}
features.toList().forEach { feature ->
private fun initFeatures(
syncParam: Int,
asyncParam: Int,
syncAction: (Feature) -> Unit,
asyncAction: (Feature) -> Unit
) {
features.values.toList().forEach { feature ->
if (feature.loadParams and syncParam != 0) {
tryInit(feature) {
syncAction(feature)