feat(social): auto sync scope

This commit is contained in:
rhunk 2023-09-21 21:06:56 +02:00
parent 8bdd7a16b4
commit f2e49e93fb
12 changed files with 165 additions and 105 deletions

View File

@ -13,6 +13,7 @@ import me.rhunk.snapenhance.core.database.objects.FriendInfo
import me.rhunk.snapenhance.core.logger.LogLevel
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
import me.rhunk.snapenhance.core.messaging.SocialScope
import me.rhunk.snapenhance.core.util.SerializableDataObject
import me.rhunk.snapenhance.download.DownloadProcessor
import kotlin.system.measureTimeMillis
@ -33,25 +34,36 @@ class BridgeService : Service() {
return BridgeBinder()
}
fun triggerFriendSync(friendId: String) {
val syncedFriend = syncCallback.syncFriend(friendId)
if (syncedFriend == null) {
remoteSideContext.log.error("Failed to sync friend $friendId")
return
fun triggerScopeSync(scope: SocialScope, id: String, updateOnly: Boolean = false) {
val modDatabase = remoteSideContext.modDatabase
val syncedObject = when (scope) {
SocialScope.FRIEND -> {
if (updateOnly && modDatabase.getFriendInfo(id) == null) return
syncCallback.syncFriend(id)
}
SocialScope.GROUP -> {
if (updateOnly && modDatabase.getGroupInfo(id) == null) return
syncCallback.syncGroup(id)
}
else -> null
}
SerializableDataObject.fromJson<FriendInfo>(syncedFriend).let {
remoteSideContext.modDatabase.syncFriend(it)
}
}
fun triggerGroupSync(groupId: String) {
val syncedGroup = syncCallback.syncGroup(groupId)
if (syncedGroup == null) {
remoteSideContext.log.error("Failed to sync group $groupId")
if (syncedObject == null) {
remoteSideContext.log.error("Failed to sync $scope $id")
return
}
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedGroup).let {
remoteSideContext.modDatabase.syncGroupInfo(it)
when (scope) {
SocialScope.FRIEND -> {
SerializableDataObject.fromJson<FriendInfo>(syncedObject).let {
modDatabase.syncFriend(it)
}
}
SocialScope.GROUP -> {
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedObject).let {
modDatabase.syncGroupInfo(it)
}
}
}
}
@ -141,14 +153,14 @@ class BridgeService : Service() {
measureTimeMillis {
remoteSideContext.modDatabase.getFriends().map { it.userId } .forEach { friendId ->
runCatching {
triggerFriendSync(friendId)
triggerScopeSync(SocialScope.FRIEND, friendId, true)
}.onFailure {
remoteSideContext.log.error("Failed to sync friend $friendId", it)
}
}
remoteSideContext.modDatabase.getGroups().map { it.conversationId }.forEach { groupId ->
runCatching {
triggerGroupSync(groupId)
triggerScopeSync(SocialScope.GROUP, groupId, true)
}.onFailure {
remoteSideContext.log.error("Failed to sync group $groupId", it)
}
@ -158,6 +170,11 @@ class BridgeService : Service() {
}
}
override fun triggerSync(scope: String, id: String) {
remoteSideContext.log.verbose("trigger sync for $scope $id")
triggerScopeSync(SocialScope.getByName(scope), id, true)
}
override fun passGroupsAndFriends(
groups: List<String>,
friends: List<String>

View File

@ -47,6 +47,7 @@ import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
import me.rhunk.snapenhance.core.messaging.SocialScope
import me.rhunk.snapenhance.core.util.snap.SnapWidgetBroadcastReceiverHelper
class AddFriendDialog(
@ -239,7 +240,7 @@ class AddFriendDialog(
getCurrentState = { context.modDatabase.getGroupInfo(group.conversationId) != null }
) { state ->
if (state) {
context.bridgeService.triggerGroupSync(group.conversationId)
context.bridgeService.triggerScopeSync(SocialScope.GROUP, group.conversationId)
} else {
context.modDatabase.deleteGroup(group.conversationId)
}
@ -266,7 +267,7 @@ class AddFriendDialog(
getCurrentState = { context.modDatabase.getFriendInfo(friend.userId) != null }
) { state ->
if (state) {
context.bridgeService.triggerFriendSync(friend.userId)
context.bridgeService.triggerScopeSync(SocialScope.FRIEND, friend.userId)
} else {
context.modDatabase.deleteFriend(friend.userId)
}

View File

@ -80,6 +80,11 @@ interface BridgeInterface {
*/
oneway void sync(SyncCallback callback);
/**
* Trigger sync for an id
*/
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)

View File

@ -26,7 +26,8 @@ class EventDispatcher(
context.classCache.conversationManager.hook("sendMessageWithContent", HookStage.BEFORE) { param ->
context.event.post(SendMessageWithContentEvent(
destinations = MessageDestinations(param.arg(0)),
messageContent = MessageContent(param.arg(1))
messageContent = MessageContent(param.arg(1)),
callback = param.arg(2)
).apply { adapter = param })?.also {
if (it.canceled) {
param.setResult(null)

View File

@ -14,10 +14,12 @@ import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.bridge.BridgeInterface
import me.rhunk.snapenhance.bridge.DownloadCallback
import me.rhunk.snapenhance.bridge.SyncCallback
import me.rhunk.snapenhance.bridge.scripting.IScripting
import me.rhunk.snapenhance.core.BuildConfig
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import me.rhunk.snapenhance.core.bridge.types.FileActionType
import me.rhunk.snapenhance.core.messaging.MessagingRuleType
import me.rhunk.snapenhance.core.messaging.SocialScope
import me.rhunk.snapenhance.data.LocalePair
import java.util.concurrent.CompletableFuture
import java.util.concurrent.Executors
@ -118,24 +120,12 @@ class BridgeClient(
fun getApplicationApkPath() = service.getApplicationApkPath()
fun getAutoUpdaterTime(): Long {
createAndReadFile(BridgeFileType.AUTO_UPDATER_TIMESTAMP, "0".toByteArray()).run {
return if (isEmpty()) {
0
} else {
String(this).toLong()
}
}
}
fun setAutoUpdaterTime(time: Long) {
writeFile(BridgeFileType.AUTO_UPDATER_TIMESTAMP, time.toString().toByteArray())
}
fun enqueueDownload(intent: Intent, callback: DownloadCallback) = service.enqueueDownload(intent, callback)
fun sync(callback: SyncCallback) = service.sync(callback)
fun triggerSync(scope: SocialScope, id: String) = service.triggerSync(scope.key, id)
fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends)
fun getRules(targetUuid: String): List<MessagingRuleType> {
@ -149,5 +139,5 @@ class BridgeClient(
fun setRule(targetUuid: String, type: MessagingRuleType, state: Boolean)
= service.setRule(targetUuid, type.key, state)
fun getScriptingInterface() = service.getScriptingInterface()
fun getScriptingInterface(): IScripting = service.getScriptingInterface()
}

View File

@ -8,8 +8,7 @@ enum class BridgeFileType(val value: Int, val fileName: String, val displayName:
CONFIG(0, "config.json", "Config"),
MAPPINGS(1, "mappings.json", "Mappings"),
MESSAGE_LOGGER_DATABASE(2, "message_logger.db", "Message Logger",true),
AUTO_UPDATER_TIMESTAMP(3, "auto_updater_timestamp.txt", "Auto Updater Timestamp"),
PINNED_CONVERSATIONS(4, "pinned_conversations.txt", "Pinned Conversations");
PINNED_CONVERSATIONS(3, "pinned_conversations.txt", "Pinned Conversations");
fun resolve(context: Context): File = if (isDatabase) {
context.getDatabasePath(fileName)

View File

@ -3,8 +3,21 @@ package me.rhunk.snapenhance.core.eventbus.events.impl
import me.rhunk.snapenhance.core.eventbus.events.AbstractHookEvent
import me.rhunk.snapenhance.data.wrapper.impl.MessageContent
import me.rhunk.snapenhance.data.wrapper.impl.MessageDestinations
import me.rhunk.snapenhance.hook.HookStage
import me.rhunk.snapenhance.hook.Hooker
class SendMessageWithContentEvent(
val destinations: MessageDestinations,
val messageContent: MessageContent
) : AbstractHookEvent()
val messageContent: MessageContent,
private val callback: Any
) : AbstractHookEvent() {
fun addCallbackResult(methodName: String, block: (args: Array<Any?>) -> Unit) {
Hooker.ephemeralHookObjectMethod(
callback::class.java,
callback,
methodName,
HookStage.BEFORE
) { block(it.args()) }
}
}

View File

@ -19,7 +19,11 @@ enum class SocialScope(
val tabRoute: String,
) {
FRIEND("friend", "friend_info/{id}"),
GROUP("group", "group_info/{id}"),
GROUP("group", "group_info/{id}");
companion object {
fun getByName(name: String) = values().first { it.key == name }
}
}
enum class MessagingRuleType(

View File

@ -0,0 +1,42 @@
package me.rhunk.snapenhance.features.impl
import kotlinx.coroutines.*
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
import me.rhunk.snapenhance.core.messaging.SocialScope
import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
class ScopeSync : Feature("Scope Sync", loadParams = FeatureLoadParams.INIT_SYNC) {
companion object {
private const val DELAY_BEFORE_SYNC = 2000L
}
private val updateJobs = mutableMapOf<String, Job>()
private val coroutineScope = CoroutineScope(Dispatchers.Main)
private fun sync(conversationId: String) {
context.database.getDMOtherParticipant(conversationId)?.also { participant ->
context.bridgeClient.triggerSync(SocialScope.FRIEND, participant)
} ?: run {
context.bridgeClient.triggerSync(SocialScope.GROUP, conversationId)
}
}
override fun init() {
context.event.subscribe(SendMessageWithContentEvent::class) { event ->
if (event.messageContent.contentType != ContentType.SNAP) return@subscribe
event.addCallbackResult("onSuccess") {
event.destinations.conversations.map { it.toString() }.forEach { conversationId ->
updateJobs[conversationId]?.also { it.cancel() }
updateJobs[conversationId] = (coroutineScope.launch {
delay(DELAY_BEFORE_SYNC)
sync(conversationId)
})
}
}
}
}
}

View File

@ -33,7 +33,7 @@ class SnapchatPlus: Feature("SnapchatPlus", loadParams = FeatureLoadParams.INIT_
it.parameterTypes[0].name != "java.lang.Boolean"
}.hook(HookStage.BEFORE) { param ->
val instance = param.thisObject<Any>()
val firstArg = param.args()[0]
val firstArg = param.arg<Any>(0)
instance::class.java.declaredFields.filter { it.type == firstArg::class.java }.forEach {
it.isAccessible = true

View File

@ -26,7 +26,7 @@ class HookAdapter(
}
fun <T : Any> argNullable(index: Int): T? {
return methodHookParam.args[index] as T?
return methodHookParam.args.getOrNull(index) as T?
}
fun setArg(index: Int, value: Any?) {
@ -34,7 +34,7 @@ class HookAdapter(
methodHookParam.args[index] = value
}
fun args(): Array<Any> {
fun args(): Array<Any?> {
return methodHookParam.args
}
@ -66,7 +66,7 @@ class HookAdapter(
invokeOriginalSafe(args(), errorCallback)
}
fun invokeOriginalSafe(args: Array<Any>, errorCallback: Consumer<Throwable>) {
fun invokeOriginalSafe(args: Array<Any?>, errorCallback: Consumer<Throwable>) {
runCatching {
setResult(XposedBridge.invokeOriginalMethod(method(), thisObject(), args))
}.onFailure {

View File

@ -6,34 +6,17 @@ import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.features.impl.ConfigurationOverride
import me.rhunk.snapenhance.features.impl.Messaging
import me.rhunk.snapenhance.features.impl.ScopeSync
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.features.impl.downloader.ProfilePictureDownloader
import me.rhunk.snapenhance.features.impl.experiments.AddFriendSourceSpoof
import me.rhunk.snapenhance.features.impl.experiments.AmoledDarkMode
import me.rhunk.snapenhance.features.impl.experiments.AppPasscode
import me.rhunk.snapenhance.features.impl.experiments.DeviceSpooferHook
import me.rhunk.snapenhance.features.impl.experiments.InfiniteStoryBoost
import me.rhunk.snapenhance.features.impl.experiments.MeoPasscodeBypass
import me.rhunk.snapenhance.features.impl.experiments.NoFriendScoreDelay
import me.rhunk.snapenhance.features.impl.experiments.UnlimitedMultiSnap
import me.rhunk.snapenhance.features.impl.experiments.*
import me.rhunk.snapenhance.features.impl.privacy.DisableMetrics
import me.rhunk.snapenhance.features.impl.privacy.PreventMessageSending
import me.rhunk.snapenhance.features.impl.spying.AnonymousStoryViewing
import me.rhunk.snapenhance.features.impl.spying.MessageLogger
import me.rhunk.snapenhance.features.impl.spying.PreventReadReceipts
import me.rhunk.snapenhance.features.impl.spying.StealthMode
import me.rhunk.snapenhance.features.impl.tweaks.AutoSave
import me.rhunk.snapenhance.features.impl.tweaks.CameraTweaks
import me.rhunk.snapenhance.features.impl.tweaks.DisableReplayInFF
import me.rhunk.snapenhance.features.impl.tweaks.DisableVideoLengthRestriction
import me.rhunk.snapenhance.features.impl.tweaks.GooglePlayServicesDialogs
import me.rhunk.snapenhance.features.impl.tweaks.LocationSpoofer
import me.rhunk.snapenhance.features.impl.tweaks.MediaQualityLevelOverride
import me.rhunk.snapenhance.features.impl.tweaks.Notifications
import me.rhunk.snapenhance.features.impl.tweaks.OldBitmojiSelfie
import me.rhunk.snapenhance.features.impl.tweaks.SendOverride
import me.rhunk.snapenhance.features.impl.tweaks.SnapchatPlus
import me.rhunk.snapenhance.features.impl.tweaks.UnlimitedSnapViewTime
import me.rhunk.snapenhance.features.impl.tweaks.*
import me.rhunk.snapenhance.features.impl.ui.ClientBootstrapOverride
import me.rhunk.snapenhance.features.impl.ui.PinConversations
import me.rhunk.snapenhance.features.impl.ui.UITweaks
@ -46,14 +29,16 @@ class FeatureManager(private val context: ModContext) : Manager {
private val asyncLoadExecutorService = Executors.newFixedThreadPool(5)
private val features = mutableListOf<Feature>()
private fun register(featureClass: KClass<out Feature>) {
runCatching {
with(featureClass.java.newInstance()) {
context = this@FeatureManager.context
features.add(this)
private fun register(vararg featureClasses: KClass<out Feature>) {
featureClasses.forEach { clazz ->
runCatching {
clazz.constructors.first().call().also { feature ->
feature.context = context
features.add(feature)
}
}.onFailure {
Logger.xposedLog("Failed to register feature ${clazz.simpleName}", it)
}
}.onFailure {
Logger.xposedLog("Failed to register feature ${featureClass.simpleName}", it)
}
}
@ -63,40 +48,43 @@ class FeatureManager(private val context: ModContext) : Manager {
}
override fun init() {
register(Messaging::class)
register(MediaDownloader::class)
register(StealthMode::class)
register(MenuViewInjector::class)
register(PreventReadReceipts::class)
register(AnonymousStoryViewing::class)
register(MessageLogger::class)
register(SnapchatPlus::class)
register(DisableMetrics::class)
register(PreventMessageSending::class)
register(Notifications::class)
register(AutoSave::class)
register(UITweaks::class)
register(ConfigurationOverride::class)
register(SendOverride::class)
register(UnlimitedSnapViewTime::class)
register(DisableVideoLengthRestriction::class)
register(MediaQualityLevelOverride::class)
register(MeoPasscodeBypass::class)
register(AppPasscode::class)
register(LocationSpoofer::class)
register(CameraTweaks::class)
register(InfiniteStoryBoost::class)
register(AmoledDarkMode::class)
register(PinConversations::class)
register(UnlimitedMultiSnap::class)
register(DeviceSpooferHook::class)
register(ClientBootstrapOverride::class)
register(GooglePlayServicesDialogs::class)
register(NoFriendScoreDelay::class)
register(ProfilePictureDownloader::class)
register(AddFriendSourceSpoof::class)
register(DisableReplayInFF::class)
register(OldBitmojiSelfie::class)
register(
ScopeSync::class,
Messaging::class,
MediaDownloader::class,
StealthMode::class,
MenuViewInjector::class,
PreventReadReceipts::class,
AnonymousStoryViewing::class,
MessageLogger::class,
SnapchatPlus::class,
DisableMetrics::class,
PreventMessageSending::class,
Notifications::class,
AutoSave::class,
UITweaks::class,
ConfigurationOverride::class,
SendOverride::class,
UnlimitedSnapViewTime::class,
DisableVideoLengthRestriction::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,
)
initializeFeatures()
}