mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-12 05:07:46 +02:00
feat: social section
- bridge: rules, sync - move extensions to a new package - snap widget broadcast receiver (SnapWidgetBroadcastReceiverHelper) - refactor compose remember delegates
This commit is contained in:
@ -2,58 +2,77 @@ package me.rhunk.snapenhance.bridge;
|
||||
|
||||
import java.util.List;
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback;
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback;
|
||||
|
||||
interface BridgeInterface {
|
||||
/**
|
||||
* Execute a file operation
|
||||
*/
|
||||
byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
|
||||
/**
|
||||
* Execute a file operation
|
||||
*/
|
||||
byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
|
||||
|
||||
/**
|
||||
* Get the content of a logged message from the database
|
||||
*
|
||||
* @param conversationId the ID of the conversation
|
||||
* @return the content of the message
|
||||
*/
|
||||
long[] getLoggedMessageIds(String conversationId, int limit);
|
||||
/**
|
||||
* Get the content of a logged message from the database
|
||||
*
|
||||
* @param conversationId the ID of the conversation
|
||||
* @return the content of the message
|
||||
*/
|
||||
long[] getLoggedMessageIds(String conversationId, int limit);
|
||||
|
||||
/**
|
||||
* Get the content of a logged message from the database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
* @return the content of the message
|
||||
*/
|
||||
@nullable byte[] getMessageLoggerMessage(String conversationId, long id);
|
||||
/**
|
||||
* Get the content of a logged message from the database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
* @return the content of the message
|
||||
*/
|
||||
@nullable byte[] getMessageLoggerMessage(String conversationId, long id);
|
||||
|
||||
/**
|
||||
* Add a message to the message logger database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
* @param message the content of the message
|
||||
*/
|
||||
void addMessageLoggerMessage(String conversationId, long id, in byte[] message);
|
||||
/**
|
||||
* Add a message to the message logger database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
* @param message the content of the message
|
||||
*/
|
||||
void addMessageLoggerMessage(String conversationId, long id, in byte[] message);
|
||||
|
||||
/**
|
||||
* Delete a message from the message logger database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
*/
|
||||
void deleteMessageLoggerMessage(String conversationId, long id);
|
||||
/**
|
||||
* Delete a message from the message logger database
|
||||
*
|
||||
* @param id the ID of the message logger message
|
||||
*/
|
||||
void deleteMessageLoggerMessage(String conversationId, long id);
|
||||
|
||||
/**
|
||||
* Clear the message logger database
|
||||
*/
|
||||
void clearMessageLogger();
|
||||
/**
|
||||
* Clear the message logger database
|
||||
*/
|
||||
void clearMessageLogger();
|
||||
|
||||
/**
|
||||
* Fetch the locales
|
||||
*
|
||||
* @return the locale result
|
||||
*/
|
||||
Map<String, String> fetchLocales(String userLocale);
|
||||
/**
|
||||
* Fetch the locales
|
||||
*
|
||||
* @return the locale result
|
||||
*/
|
||||
Map<String, String> fetchLocales(String userLocale);
|
||||
|
||||
/**
|
||||
* Enqueue a download
|
||||
*/
|
||||
void enqueueDownload(in Intent intent, DownloadCallback callback);
|
||||
/**
|
||||
* Enqueue a download
|
||||
*/
|
||||
void enqueueDownload(in Intent intent, DownloadCallback callback);
|
||||
|
||||
/**
|
||||
* Get rules for a given user or conversation
|
||||
*/
|
||||
|
||||
List<String> getRules(String objectType, String uuid);
|
||||
|
||||
/**
|
||||
* Sync groups and friends
|
||||
*/
|
||||
oneway void sync(SyncCallback callback);
|
||||
|
||||
/**
|
||||
* Pass all groups and friends to be able to add them to the database
|
||||
* @param groups serialized groups
|
||||
* @param friends serialized friends
|
||||
*/
|
||||
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package me.rhunk.snapenhance.bridge;
|
||||
|
||||
interface SyncCallback {
|
||||
/**
|
||||
* Called when the friend data has been synced
|
||||
* @param uuid The uuid of the friend to sync
|
||||
* @return The serialized friend data
|
||||
*/
|
||||
String syncFriend(String uuid);
|
||||
|
||||
/**
|
||||
* Called when the conversation data has been synced
|
||||
* @param uuid The uuid of the conversation to sync
|
||||
* @return The serialized conversation data
|
||||
*/
|
||||
String syncGroup(String uuid);
|
||||
}
|
@ -20,7 +20,7 @@
|
||||
"downloads": "Downloads",
|
||||
"features": "Features",
|
||||
"home": "Home",
|
||||
"friends": "Friends",
|
||||
"social": "Social",
|
||||
"plugins": "Plugins"
|
||||
},
|
||||
"features": {
|
||||
|
@ -1,12 +1,15 @@
|
||||
package me.rhunk.snapenhance
|
||||
|
||||
import android.content.Intent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.OnSnapInteractionEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.MessageContent
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
|
||||
|
||||
class EventDispatcher(
|
||||
private val context: ModContext
|
||||
@ -14,7 +17,7 @@ class EventDispatcher(
|
||||
override fun init() {
|
||||
context.classCache.conversationManager.hook("sendMessageWithContent", HookStage.BEFORE) { param ->
|
||||
val messageContent = MessageContent(param.arg(1))
|
||||
context.event.post(SendMessageWithContentEvent(messageContent).apply { adapter = param })?.let {
|
||||
context.event.post(SendMessageWithContentEvent(messageContent).apply { adapter = param })?.also {
|
||||
if (it.canceled) {
|
||||
param.setResult(null)
|
||||
}
|
||||
@ -29,7 +32,26 @@ class EventDispatcher(
|
||||
conversationId = conversationId,
|
||||
messageId = messageId
|
||||
)
|
||||
)?.let {
|
||||
)?.also {
|
||||
if (it.canceled) {
|
||||
param.setResult(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
context.androidContext.classLoader.loadClass(SnapWidgetBroadcastReceiverHelper.CLASS_NAME)
|
||||
.hook("onReceive", HookStage.BEFORE) { param ->
|
||||
val intent = param.arg(1) as? Intent ?: return@hook
|
||||
if (!SnapWidgetBroadcastReceiverHelper.isIncomingIntentValid(intent)) return@hook
|
||||
val action = intent.getStringExtra("action") ?: return@hook
|
||||
|
||||
context.event.post(
|
||||
SnapWidgetBroadcastReceiveEvent(
|
||||
androidContext = context.androidContext,
|
||||
intent = intent,
|
||||
action = action
|
||||
)
|
||||
)?.also {
|
||||
if (it.canceled) {
|
||||
param.setResult(null)
|
||||
}
|
||||
|
@ -1,34 +0,0 @@
|
||||
package me.rhunk.snapenhance
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.AlertDialog
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.Settings
|
||||
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.download.DownloadTaskManager
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Used to store objects between activities and receivers
|
||||
*/
|
||||
object SharedContext {
|
||||
lateinit var downloadTaskManager: DownloadTaskManager
|
||||
lateinit var translation: LocaleWrapper
|
||||
|
||||
fun ensureInitialized(context: Context) {
|
||||
if (!this::downloadTaskManager.isInitialized) {
|
||||
downloadTaskManager = DownloadTaskManager().apply {
|
||||
init(context)
|
||||
}
|
||||
}
|
||||
if (!this::translation.isInitialized) {
|
||||
translation = LocaleWrapper().apply {
|
||||
loadFromContext(context)
|
||||
}
|
||||
}
|
||||
//askForPermissions(context)
|
||||
}
|
||||
}
|
@ -7,12 +7,16 @@ import android.content.pm.PackageManager
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||
import me.rhunk.snapenhance.data.SnapClassCache
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.util.getApplicationInfoCompat
|
||||
import me.rhunk.snapenhance.util.ktx.getApplicationInfoCompat
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTime
|
||||
|
||||
@ -104,6 +108,7 @@ class SnapEnhance {
|
||||
//if mappings aren't loaded, we can't initialize features
|
||||
if (!mappings.isMappingsLoaded()) return
|
||||
features.init()
|
||||
syncRemote()
|
||||
}
|
||||
}.also { time ->
|
||||
Logger.debug("init took $time")
|
||||
@ -121,4 +126,53 @@ class SnapEnhance {
|
||||
Logger.debug("onActivityCreate took $time")
|
||||
}
|
||||
}
|
||||
|
||||
private fun syncRemote() {
|
||||
val database = appContext.database
|
||||
|
||||
appContext.bridgeClient.sync(object : SyncCallback.Stub() {
|
||||
override fun syncFriend(uuid: String): String? {
|
||||
return database.getFriendInfo(uuid)?.toJson()
|
||||
}
|
||||
|
||||
override fun syncGroup(uuid: String): String? {
|
||||
return database.getFeedEntryByConversationId(uuid)?.let {
|
||||
MessagingGroupInfo(
|
||||
it.key!!,
|
||||
it.feedDisplayName!!,
|
||||
it.participantsSize
|
||||
).toJson()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
appContext.event.subscribe(SnapWidgetBroadcastReceiveEvent::class) { event ->
|
||||
if (event.action != BridgeClient.BRIDGE_SYNC_ACTION) return@subscribe
|
||||
event.canceled = true
|
||||
val feedEntries = appContext.database.getFeedEntries(Int.MAX_VALUE)
|
||||
|
||||
val groups = feedEntries.filter { it.friendUserId == null }.map {
|
||||
MessagingGroupInfo(
|
||||
it.key!!,
|
||||
it.feedDisplayName!!,
|
||||
it.participantsSize
|
||||
)
|
||||
}
|
||||
|
||||
val friends = feedEntries.filter { it.friendUserId != null }.map {
|
||||
MessagingFriendInfo(
|
||||
it.friendUserId!!,
|
||||
it.friendDisplayName,
|
||||
it.friendDisplayUsername!!.split("|")[1],
|
||||
it.bitmojiAvatarId,
|
||||
it.bitmojiSelfieId
|
||||
)
|
||||
}
|
||||
|
||||
appContext.bridgeClient.passGroupsAndFriends(
|
||||
groups.map { it.toJson() },
|
||||
friends.map { it.toJson() }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,7 @@ import me.rhunk.snapenhance.action.AbstractAction
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.database.objects.FriendFeedInfo
|
||||
import me.rhunk.snapenhance.database.objects.FriendFeedEntry
|
||||
import me.rhunk.snapenhance.features.impl.Messaging
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.util.CallbackBuilder
|
||||
@ -108,8 +108,8 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
exportType = askExportType() ?: return@launch
|
||||
mediaToDownload = if (exportType == ExportFormat.HTML) askMediaToDownload() else null
|
||||
|
||||
val friendFeedEntries = context.database.getFriendFeed(20)
|
||||
val selectedConversations = mutableListOf<FriendFeedInfo>()
|
||||
val friendFeedEntries = context.database.getFeedEntries(20)
|
||||
val selectedConversations = mutableListOf<FriendFeedEntry>()
|
||||
|
||||
ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
|
||||
.setTitle(context.translation["chat_export.select_conversation"])
|
||||
@ -182,12 +182,12 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun exportFullConversation(friendFeedInfo: FriendFeedInfo) {
|
||||
private suspend fun exportFullConversation(friendFeedEntry: FriendFeedEntry) {
|
||||
//first fetch the first message
|
||||
val conversationId = friendFeedInfo.key!!
|
||||
val conversationName = friendFeedInfo.feedDisplayName ?: friendFeedInfo.friendDisplayName!!.split("|").lastOrNull() ?: "unknown"
|
||||
val conversationId = friendFeedEntry.key!!
|
||||
val conversationName = friendFeedEntry.feedDisplayName ?: friendFeedEntry.friendDisplayName!!.split("|").lastOrNull() ?: "unknown"
|
||||
|
||||
conversationAction(true, conversationId, if (friendFeedInfo.feedDisplayName != null) "USERCREATEDGROUP" else "ONEONONE")
|
||||
conversationAction(true, conversationId, if (friendFeedEntry.feedDisplayName != null) "USERCREATEDGROUP" else "ONEONONE")
|
||||
|
||||
logDialog(context.translation.format("chat_export.exporting_message", "conversation" to conversationName))
|
||||
|
||||
@ -215,7 +215,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
logDialog(context.translation["chat_export.writing_output"])
|
||||
MessageExporter(
|
||||
context = context,
|
||||
friendFeedInfo = friendFeedInfo,
|
||||
friendFeedEntry = friendFeedEntry,
|
||||
outputFile = outputFile,
|
||||
mediaToDownload = mediaToDownload,
|
||||
printLog = ::logDialog
|
||||
@ -245,7 +245,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
|
||||
}
|
||||
}
|
||||
|
||||
private fun exportChatForConversations(conversations: List<FriendFeedInfo>) {
|
||||
private fun exportChatForConversations(conversations: List<FriendFeedEntry>) {
|
||||
dialogLogs.clear()
|
||||
val jobs = mutableListOf<Job>()
|
||||
|
||||
|
@ -15,7 +15,10 @@ import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.bridge.types.FileActionType
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingRule
|
||||
import me.rhunk.snapenhance.core.messaging.RuleScope
|
||||
import me.rhunk.snapenhance.data.LocalePair
|
||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.system.exitProcess
|
||||
@ -27,6 +30,10 @@ class BridgeClient(
|
||||
private lateinit var future: CompletableFuture<Boolean>
|
||||
private lateinit var service: BridgeInterface
|
||||
|
||||
companion object {
|
||||
const val BRIDGE_SYNC_ACTION = "me.rhunk.snapenhance.bridge.SYNC"
|
||||
}
|
||||
|
||||
fun start(callback: (Boolean) -> Unit) {
|
||||
this.future = CompletableFuture()
|
||||
|
||||
@ -124,4 +131,14 @@ class BridgeClient(
|
||||
}
|
||||
|
||||
fun enqueueDownload(intent: Intent, callback: DownloadCallback) = service.enqueueDownload(intent, callback)
|
||||
|
||||
fun sync(callback: SyncCallback) = service.sync(callback)
|
||||
|
||||
fun passGroupsAndFriends(groups: List<String>, friends: List<String>) = service.passGroupsAndFriends(groups, friends)
|
||||
|
||||
fun getRulesFromId(type: RuleScope, targetUuid: String): List<MessagingRule> {
|
||||
return service.getRules(type.name, targetUuid).map {
|
||||
SerializableDataObject.fromJson(it, MessagingRule::class.java)
|
||||
}.toList()
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,11 @@ class EventBus(
|
||||
val obj = object : IListener<T> {
|
||||
override fun handle(event: T) {
|
||||
if (!filter(event)) return
|
||||
listener(event)
|
||||
runCatching {
|
||||
listener(event)
|
||||
}.onFailure {
|
||||
Logger.error("Error while handling event ${event::class.simpleName}", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
subscribe(event, obj)
|
||||
|
@ -0,0 +1,11 @@
|
||||
package me.rhunk.snapenhance.core.eventbus.events.impl
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import me.rhunk.snapenhance.core.eventbus.events.AbstractHookEvent
|
||||
|
||||
class SnapWidgetBroadcastReceiveEvent(
|
||||
val androidContext: Context,
|
||||
val intent: Intent?,
|
||||
val action: String
|
||||
) : AbstractHookEvent()
|
@ -1,10 +0,0 @@
|
||||
package me.rhunk.snapenhance.core.messaging
|
||||
|
||||
enum class EnumConversationFeature(
|
||||
val value: String,
|
||||
val objectType: ObjectType,
|
||||
) {
|
||||
DOWNLOAD("download", ObjectType.USER),
|
||||
STEALTH("stealth", ObjectType.CONVERSATION),
|
||||
AUTO_SAVE("auto_save", ObjectType.CONVERSATION);
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package me.rhunk.snapenhance.core.messaging
|
||||
|
||||
|
||||
enum class Mode {
|
||||
BLACKLIST,
|
||||
WHITELIST
|
||||
}
|
||||
|
||||
enum class ObjectType {
|
||||
USER,
|
||||
CONVERSATION
|
||||
}
|
||||
|
||||
data class FriendStreaks(
|
||||
val userId: String,
|
||||
val notify: Boolean,
|
||||
val expirationTimestamp: Long,
|
||||
val count: Int
|
||||
)
|
||||
|
||||
data class MessagingRule(
|
||||
val id: Int,
|
||||
val objectType: ObjectType,
|
||||
val targetUuid: String,
|
||||
val enabled: Boolean,
|
||||
val mode: Mode?,
|
||||
val subject: String
|
||||
)
|
@ -0,0 +1,55 @@
|
||||
package me.rhunk.snapenhance.core.messaging
|
||||
|
||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||
|
||||
|
||||
enum class Mode {
|
||||
BLACKLIST,
|
||||
WHITELIST
|
||||
}
|
||||
|
||||
enum class RuleScope {
|
||||
FRIEND,
|
||||
GROUP
|
||||
}
|
||||
|
||||
enum class ConversationFeature(
|
||||
val value: String,
|
||||
val ruleScope: RuleScope,
|
||||
) {
|
||||
DOWNLOAD("download", RuleScope.FRIEND),
|
||||
STEALTH("stealth", RuleScope.GROUP),
|
||||
AUTO_SAVE("auto_save", RuleScope.GROUP);
|
||||
}
|
||||
|
||||
data class FriendStreaks(
|
||||
val userId: String,
|
||||
val notify: Boolean,
|
||||
val expirationTimestamp: Long,
|
||||
val count: Int
|
||||
) : SerializableDataObject()
|
||||
|
||||
|
||||
data class MessagingGroupInfo(
|
||||
val conversationId: String,
|
||||
val name: String,
|
||||
val participantsCount: Int
|
||||
) : SerializableDataObject()
|
||||
|
||||
data class MessagingFriendInfo(
|
||||
val userId: String,
|
||||
val displayName: String?,
|
||||
val mutableUsername: String,
|
||||
val bitmojiId: String?,
|
||||
val selfieId: String?
|
||||
) : SerializableDataObject()
|
||||
|
||||
|
||||
data class MessagingRule(
|
||||
val id: Int,
|
||||
val ruleScope: RuleScope,
|
||||
val targetUuid: String,
|
||||
val enabled: Boolean,
|
||||
val mode: Mode?,
|
||||
val subject: String
|
||||
) : SerializableDataObject()
|
@ -2,7 +2,7 @@ package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.data.MessageState
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
|
||||
class Message(obj: Any?) : AbstractWrapper(obj) {
|
||||
val orderKey get() = instanceNonNull().getObjectField("mOrderKey") as Long
|
||||
|
@ -2,8 +2,8 @@ package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.setObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
||||
|
||||
class MessageContent(obj: Any?) : AbstractWrapper(obj) {
|
||||
var content
|
||||
|
@ -1,7 +1,7 @@
|
||||
package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
|
||||
class MessageDescriptor(obj: Any?) : AbstractWrapper(obj) {
|
||||
val messageId: Long get() = instanceNonNull().getObjectField("mMessageId") as Long
|
||||
|
@ -1,8 +1,8 @@
|
||||
package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.setObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MessageDestinations(obj: Any) : AbstractWrapper(obj){
|
||||
|
@ -2,7 +2,7 @@ package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.data.PlayableSnapState
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
|
||||
class MessageMetadata(obj: Any?) : AbstractWrapper(obj){
|
||||
val createdAt: Long get() = instanceNonNull().getObjectField("mCreatedAt") as Long
|
||||
|
@ -2,7 +2,7 @@ package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.SnapEnhance
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.UUID
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
package me.rhunk.snapenhance.data.wrapper.impl
|
||||
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
|
||||
class UserIdToReaction(obj: Any?) : AbstractWrapper(obj) {
|
||||
val userId = SnapUUID(instanceNonNull().getObjectField("mUserId"))
|
||||
|
@ -2,7 +2,7 @@ package me.rhunk.snapenhance.data.wrapper.impl.media
|
||||
|
||||
import android.os.Parcelable
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import java.lang.reflect.Field
|
||||
|
||||
|
||||
|
@ -2,7 +2,7 @@ package me.rhunk.snapenhance.data.wrapper.impl.media.opera
|
||||
|
||||
import me.rhunk.snapenhance.data.wrapper.AbstractWrapper
|
||||
import me.rhunk.snapenhance.util.ReflectionHelper
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import java.lang.reflect.Field
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
|
@ -74,10 +74,10 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
||||
return obj
|
||||
}
|
||||
|
||||
fun getFriendFeedInfoByUserId(userId: String): FriendFeedInfo? {
|
||||
fun getFeedEntryByUserId(userId: String): FriendFeedEntry? {
|
||||
return safeDatabaseOperation(openMain()) { database ->
|
||||
readDatabaseObject(
|
||||
FriendFeedInfo(),
|
||||
FriendFeedEntry(),
|
||||
database,
|
||||
"FriendsFeedView",
|
||||
"friendUserId = ?",
|
||||
@ -86,10 +86,10 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
||||
}
|
||||
}
|
||||
|
||||
fun getFriendFeedInfoByConversationId(conversationId: String): FriendFeedInfo? {
|
||||
fun getFeedEntryByConversationId(conversationId: String): FriendFeedEntry? {
|
||||
return safeDatabaseOperation(openMain()) {
|
||||
readDatabaseObject(
|
||||
FriendFeedInfo(),
|
||||
FriendFeedEntry(),
|
||||
it,
|
||||
"FriendsFeedView",
|
||||
"key = ?",
|
||||
@ -110,19 +110,19 @@ class DatabaseAccess(private val context: ModContext) : Manager {
|
||||
}
|
||||
}
|
||||
|
||||
fun getFriendFeed(limit: Int): List<FriendFeedInfo> {
|
||||
fun getFeedEntries(limit: Int): List<FriendFeedEntry> {
|
||||
return safeDatabaseOperation(openMain()) { database ->
|
||||
val cursor = database.rawQuery(
|
||||
"SELECT * FROM FriendsFeedView ORDER BY _id LIMIT ?",
|
||||
arrayOf(limit.toString())
|
||||
)
|
||||
val list = mutableListOf<FriendFeedInfo>()
|
||||
val list = mutableListOf<FriendFeedEntry>()
|
||||
while (cursor.moveToNext()) {
|
||||
val friendFeedInfo = FriendFeedInfo()
|
||||
val friendFeedEntry = FriendFeedEntry()
|
||||
try {
|
||||
friendFeedInfo.write(cursor)
|
||||
friendFeedEntry.write(cursor)
|
||||
} catch (_: Throwable) {}
|
||||
list.add(friendFeedInfo)
|
||||
list.add(friendFeedEntry)
|
||||
}
|
||||
cursor.close()
|
||||
list
|
||||
|
@ -5,10 +5,10 @@ import android.database.Cursor
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.getBlobOrNull
|
||||
import me.rhunk.snapenhance.util.getInteger
|
||||
import me.rhunk.snapenhance.util.getLong
|
||||
import me.rhunk.snapenhance.util.getStringOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getBlobOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getLong
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
|
@ -3,21 +3,26 @@ package me.rhunk.snapenhance.database.objects
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.getInteger
|
||||
import me.rhunk.snapenhance.util.getLong
|
||||
import me.rhunk.snapenhance.util.getStringOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getLong
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
|
||||
data class FriendFeedInfo(
|
||||
data class FriendFeedEntry(
|
||||
var id: Int = 0,
|
||||
var feedDisplayName: String? = null,
|
||||
var participantsSize: Int = 0,
|
||||
var lastInteractionTimestamp: Long = 0,
|
||||
var displayTimestamp: Long = 0,
|
||||
var displayInteractionType: String? = null,
|
||||
var lastInteractionUserId: Int = 0,
|
||||
var lastInteractionUserId: Int? = null,
|
||||
var key: String? = null,
|
||||
var friendUserId: String? = null,
|
||||
var friendDisplayName: String? = null,
|
||||
var friendDisplayUsername: String? = null,
|
||||
var friendLinkType: Int? = null,
|
||||
var bitmojiAvatarId: String? = null,
|
||||
var bitmojiSelfieId: String? = null,
|
||||
) : DatabaseObject {
|
||||
|
||||
@SuppressLint("Range")
|
||||
@ -29,10 +34,14 @@ data class FriendFeedInfo(
|
||||
lastInteractionTimestamp = getLong("lastInteractionTimestamp")
|
||||
displayTimestamp = getLong("displayTimestamp")
|
||||
displayInteractionType = getStringOrNull("displayInteractionType")
|
||||
lastInteractionUserId = getInteger("lastInteractionUserId")
|
||||
lastInteractionUserId = getIntOrNull("lastInteractionUserId")
|
||||
key = getStringOrNull("key")
|
||||
friendUserId = getStringOrNull("friendUserId")
|
||||
friendDisplayName = getStringOrNull("friendDisplayUsername")
|
||||
friendDisplayName = getStringOrNull("friendDisplayName")
|
||||
friendDisplayUsername = getStringOrNull("friendDisplayUsername")
|
||||
friendLinkType = getIntOrNull("friendLinkType")
|
||||
bitmojiAvatarId = getStringOrNull("bitmojiAvatarId")
|
||||
bitmojiSelfieId = getStringOrNull("bitmojiSelfieId")
|
||||
}
|
||||
}
|
||||
}
|
@ -3,9 +3,10 @@ package me.rhunk.snapenhance.database.objects
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.getInteger
|
||||
import me.rhunk.snapenhance.util.getLong
|
||||
import me.rhunk.snapenhance.util.getStringOrNull
|
||||
import me.rhunk.snapenhance.util.SerializableDataObject
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getLong
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
|
||||
data class FriendInfo(
|
||||
var id: Int = 0,
|
||||
@ -30,7 +31,7 @@ data class FriendInfo(
|
||||
var isPinnedBestFriend: Int = 0,
|
||||
var plusBadgeVisibility: Int = 0,
|
||||
var usernameForSorting: String? = null
|
||||
) : DatabaseObject {
|
||||
) : DatabaseObject, SerializableDataObject() {
|
||||
@SuppressLint("Range")
|
||||
override fun write(cursor: Cursor) {
|
||||
with(cursor) {
|
||||
|
@ -3,8 +3,8 @@ package me.rhunk.snapenhance.database.objects
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.getInteger
|
||||
import me.rhunk.snapenhance.util.getStringOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
|
||||
data class StoryEntry(
|
||||
var id: Int = 0,
|
||||
|
@ -3,8 +3,8 @@ package me.rhunk.snapenhance.database.objects
|
||||
import android.annotation.SuppressLint
|
||||
import android.database.Cursor
|
||||
import me.rhunk.snapenhance.database.DatabaseObject
|
||||
import me.rhunk.snapenhance.util.getInteger
|
||||
import me.rhunk.snapenhance.util.getStringOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getInteger
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
|
||||
class UserConversationLink(
|
||||
var userId: String? = null,
|
||||
|
@ -8,8 +8,8 @@ import me.rhunk.snapenhance.download.data.DownloadObject
|
||||
import me.rhunk.snapenhance.download.data.DownloadStage
|
||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
|
||||
import me.rhunk.snapenhance.util.getIntOrNull
|
||||
import me.rhunk.snapenhance.util.getStringOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getIntOrNull
|
||||
import me.rhunk.snapenhance.util.ktx.getStringOrNull
|
||||
|
||||
class DownloadTaskManager {
|
||||
private lateinit var taskDatabase: SQLiteDatabase
|
||||
|
@ -5,7 +5,7 @@ import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.util.setObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
||||
|
||||
class ConfigurationOverride : Feature("Configuration Override", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||
override fun init() {
|
||||
|
@ -8,7 +8,7 @@ import me.rhunk.snapenhance.features.impl.spying.StealthMode
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
|
||||
class Messaging : Feature("Messaging", loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC or FeatureLoadParams.INIT_ASYNC or FeatureLoadParams.INIT_SYNC) {
|
||||
lateinit var conversationManager: Any
|
||||
|
@ -6,7 +6,6 @@ import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.widget.ImageView
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.rhunk.snapenhance.Constants.ARROYO_URL_KEY_PROTO_PATH
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.Logger.xposedLog
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback
|
||||
@ -22,6 +21,7 @@ import me.rhunk.snapenhance.download.DownloadManagerClient
|
||||
import me.rhunk.snapenhance.download.data.DownloadMediaType
|
||||
import me.rhunk.snapenhance.download.data.DownloadMetadata
|
||||
import me.rhunk.snapenhance.download.data.InputMedia
|
||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
|
||||
import me.rhunk.snapenhance.download.data.toKeyPair
|
||||
import me.rhunk.snapenhance.features.Feature
|
||||
@ -32,9 +32,8 @@ import me.rhunk.snapenhance.hook.HookAdapter
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapenhance.download.data.MediaFilter
|
||||
import me.rhunk.snapenhance.util.download.RemoteMediaResolver
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.util.snap.BitmojiSelfie
|
||||
import me.rhunk.snapenhance.util.snap.EncryptionHelper
|
||||
|
@ -4,7 +4,7 @@ import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.util.setObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
||||
|
||||
class UnlimitedMultiSnap : Feature("UnlimitedMultiSnap", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
override fun asyncOnActivityCreate() {
|
||||
|
@ -4,7 +4,7 @@ import me.rhunk.snapenhance.features.Feature
|
||||
import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
|
||||
class AnonymousStoryViewing : Feature("Anonymous Story Viewing", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
override fun asyncOnActivityCreate() {
|
||||
|
@ -71,7 +71,7 @@ class MessageLogger : Feature("MessageLogger",
|
||||
}
|
||||
|
||||
measureTime {
|
||||
context.database.getFriendFeed(PREFETCH_FEED_COUNT).forEach { friendFeedInfo ->
|
||||
context.database.getFeedEntries(PREFETCH_FEED_COUNT).forEach { friendFeedInfo ->
|
||||
fetchedMessages.addAll(context.bridgeClient.getLoggedMessageIds(friendFeedInfo.key!!, PREFETCH_MESSAGE_COUNT).toList())
|
||||
}
|
||||
}.also { Logger.debug("Loaded ${fetchedMessages.size} cached messages in $it") }
|
||||
|
@ -12,7 +12,7 @@ import me.rhunk.snapenhance.features.impl.spying.StealthMode
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.Hooker
|
||||
import me.rhunk.snapenhance.util.CallbackBuilder
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class AutoSave : Feature("Auto Save", loadParams = FeatureLoadParams.ACTIVITY_CREATE_ASYNC) {
|
||||
|
@ -12,8 +12,8 @@ import android.os.Bundle
|
||||
import android.os.UserHandle
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
|
||||
import me.rhunk.snapenhance.data.ContentType
|
||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
@ -31,6 +31,7 @@ import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.util.snap.EncryptionHelper
|
||||
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
||||
import me.rhunk.snapenhance.util.snap.PreviewUtils
|
||||
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
|
||||
|
||||
class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.INIT_SYNC) {
|
||||
companion object{
|
||||
@ -42,10 +43,6 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
||||
private val cachedMessages = mutableMapOf<String, MutableList<String>>() // conversationId => cached messages
|
||||
private val notificationIdMap = mutableMapOf<Int, String>() // notificationId => conversationId
|
||||
|
||||
private val broadcastReceiverClass by lazy {
|
||||
context.androidContext.classLoader.loadClass("com.snap.widgets.core.BestFriendsWidgetProvider")
|
||||
}
|
||||
|
||||
private val notifyAsUserMethod by lazy {
|
||||
XposedHelpers.findMethodExact(
|
||||
NotificationManager::class.java, "notifyAsUser",
|
||||
@ -102,16 +99,18 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
||||
|
||||
fun newAction(title: String, remoteAction: String, filter: (() -> Boolean), builder: (Notification.Action.Builder) -> Unit) {
|
||||
if (!filter()) return
|
||||
val intent = Intent().setClassName(Constants.SNAPCHAT_PACKAGE_NAME, broadcastReceiverClass.name)
|
||||
.putExtra("conversation_id", conversationId)
|
||||
.putExtra("notification_id", notificationData.id)
|
||||
.putExtra("message_id", messageId)
|
||||
.setAction(remoteAction)
|
||||
|
||||
val intent = SnapWidgetBroadcastReceiverHelper.create(remoteAction) {
|
||||
putExtra("conversation_id", conversationId)
|
||||
putExtra("notification_id", notificationData.id)
|
||||
putExtra("message_id", messageId)
|
||||
}
|
||||
|
||||
val action = Notification.Action.Builder(null, title, PendingIntent.getBroadcast(
|
||||
context.androidContext,
|
||||
System.nanoTime().toInt(),
|
||||
intent,
|
||||
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
|
||||
PendingIntent.FLAG_MUTABLE
|
||||
)).apply(builder).build()
|
||||
actions.add(action)
|
||||
}
|
||||
@ -134,14 +133,12 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
||||
}
|
||||
|
||||
private fun setupBroadcastReceiverHook() {
|
||||
Hooker.hook(broadcastReceiverClass, "onReceive", HookStage.BEFORE) { param ->
|
||||
val androidContext = param.arg<Context>(0)
|
||||
val intent = param.arg<Intent>(1)
|
||||
|
||||
val conversationId = intent.getStringExtra("conversation_id") ?: return@hook
|
||||
context.event.subscribe(SnapWidgetBroadcastReceiveEvent::class) { event ->
|
||||
val intent = event.intent ?: return@subscribe
|
||||
val conversationId = intent.getStringExtra("conversation_id") ?: return@subscribe
|
||||
val messageId = intent.getLongExtra("message_id", -1)
|
||||
val notificationId = intent.getIntExtra("notification_id", -1)
|
||||
val notificationManager = androidContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
val notificationManager = event.androidContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
|
||||
val updateNotification: (Int, (Notification) -> Unit) -> Unit = { id, notificationBuilder ->
|
||||
notificationManager.activeNotifications.firstOrNull { it.id == id }?.let {
|
||||
@ -152,7 +149,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
||||
}
|
||||
}
|
||||
|
||||
when (intent.action) {
|
||||
when (event.action) {
|
||||
ACTION_REPLY -> {
|
||||
val input = RemoteInput.getResultsFromIntent(intent).getCharSequence("chat_reply_input")
|
||||
.toString()
|
||||
@ -177,10 +174,10 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
|
||||
context.longToast(it)
|
||||
}
|
||||
}
|
||||
else -> return@hook
|
||||
else -> return@subscribe
|
||||
}
|
||||
|
||||
param.setResult(null)
|
||||
event.canceled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,8 @@ import me.rhunk.snapenhance.features.FeatureLoadParams
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
import me.rhunk.snapenhance.hook.hookConstructor
|
||||
import me.rhunk.snapenhance.util.getObjectField
|
||||
import me.rhunk.snapenhance.util.setObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.getObjectField
|
||||
import me.rhunk.snapenhance.util.ktx.setObjectField
|
||||
|
||||
class PinConversations : BridgeFileFeature("PinConversations", BridgeFileType.PINNED_CONVERSATIONS, loadParams = FeatureLoadParams.ACTIVITY_CREATE_SYNC) {
|
||||
override fun onActivityCreate() {
|
||||
|
@ -1,189 +0,0 @@
|
||||
package me.rhunk.snapenhance.manager.impl
|
||||
|
||||
import com.google.gson.JsonElement
|
||||
import com.google.gson.JsonParser
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.Logger
|
||||
import me.rhunk.snapenhance.ModContext
|
||||
import me.rhunk.snapenhance.bridge.types.BridgeFileType
|
||||
import me.rhunk.snapenhance.manager.Manager
|
||||
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
|
||||
import me.rhunk.snapmapper.Mapper
|
||||
import me.rhunk.snapmapper.impl.BCryptClassMapper
|
||||
import me.rhunk.snapmapper.impl.CallbackMapper
|
||||
import me.rhunk.snapmapper.impl.CompositeConfigurationProviderMapper
|
||||
import me.rhunk.snapmapper.impl.DefaultMediaItemMapper
|
||||
import me.rhunk.snapmapper.impl.EnumMapper
|
||||
import me.rhunk.snapmapper.impl.FriendsFeedEventDispatcherMapper
|
||||
import me.rhunk.snapmapper.impl.MediaQualityLevelProviderMapper
|
||||
import me.rhunk.snapmapper.impl.OperaPageViewControllerMapper
|
||||
import me.rhunk.snapmapper.impl.PlatformAnalyticsCreatorMapper
|
||||
import me.rhunk.snapmapper.impl.PlusSubscriptionMapper
|
||||
import me.rhunk.snapmapper.impl.ScCameraSettingsMapper
|
||||
import me.rhunk.snapmapper.impl.ScoreUpdateMapper
|
||||
import me.rhunk.snapmapper.impl.StoryBoostStateMapper
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
class MappingManager(private val context: ModContext) : Manager {
|
||||
private val mappers = arrayOf(
|
||||
BCryptClassMapper::class,
|
||||
CallbackMapper::class,
|
||||
DefaultMediaItemMapper::class,
|
||||
MediaQualityLevelProviderMapper::class,
|
||||
EnumMapper::class,
|
||||
OperaPageViewControllerMapper::class,
|
||||
PlatformAnalyticsCreatorMapper::class,
|
||||
PlusSubscriptionMapper::class,
|
||||
ScCameraSettingsMapper::class,
|
||||
StoryBoostStateMapper::class,
|
||||
FriendsFeedEventDispatcherMapper::class,
|
||||
CompositeConfigurationProviderMapper::class,
|
||||
ScoreUpdateMapper::class
|
||||
)
|
||||
|
||||
private val mappings = ConcurrentHashMap<String, Any>()
|
||||
val areMappingsLoaded: Boolean
|
||||
get() = mappings.isNotEmpty()
|
||||
private var snapBuildNumber = 0
|
||||
|
||||
@Suppress("deprecation")
|
||||
override fun init() {
|
||||
val currentBuildNumber = context.androidContext.packageManager.getPackageInfo(
|
||||
Constants.SNAPCHAT_PACKAGE_NAME,
|
||||
0
|
||||
).longVersionCode.toInt()
|
||||
snapBuildNumber = currentBuildNumber
|
||||
|
||||
if (context.bridgeClient.isFileExists(BridgeFileType.MAPPINGS)) {
|
||||
runCatching {
|
||||
loadCached()
|
||||
}.onFailure {
|
||||
context.crash("Failed to load cached mappings ${it.message}", it)
|
||||
}
|
||||
|
||||
if (snapBuildNumber != currentBuildNumber) {
|
||||
context.bridgeClient.deleteFile(BridgeFileType.MAPPINGS)
|
||||
context.softRestartApp()
|
||||
}
|
||||
return
|
||||
}
|
||||
context.runOnUiThread {
|
||||
val statusDialogBuilder = ViewAppearanceHelper.newAlertDialogBuilder(context.mainActivity)
|
||||
.setMessage("Generating mappings, please wait...")
|
||||
.setCancelable(false)
|
||||
.setView(android.widget.ProgressBar(context.mainActivity).apply {
|
||||
setPadding(0, 20, 0, 20)
|
||||
})
|
||||
|
||||
val loadingDialog = statusDialogBuilder.show()
|
||||
|
||||
context.executeAsync {
|
||||
runCatching {
|
||||
refresh()
|
||||
}.onSuccess {
|
||||
context.shortToast("Generated mappings for build $snapBuildNumber")
|
||||
context.softRestartApp()
|
||||
}.onFailure {
|
||||
Logger.error("Failed to generate mappings", it)
|
||||
context.runOnUiThread {
|
||||
loadingDialog.dismiss()
|
||||
statusDialogBuilder.setView(null)
|
||||
statusDialogBuilder.setMessage("Failed to generate mappings: $it")
|
||||
statusDialogBuilder.setNegativeButton("Close") { _, _ ->
|
||||
context.mainActivity!!.finish()
|
||||
}
|
||||
statusDialogBuilder.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCached() {
|
||||
if (!context.bridgeClient.isFileExists(BridgeFileType.MAPPINGS)) {
|
||||
Logger.xposedLog("Mappings file does not exist")
|
||||
return
|
||||
}
|
||||
val mappingsObject = JsonParser.parseString(
|
||||
String(
|
||||
context.bridgeClient.readFile(BridgeFileType.MAPPINGS),
|
||||
StandardCharsets.UTF_8
|
||||
)
|
||||
).asJsonObject.also {
|
||||
snapBuildNumber = it["snap_build_number"].asInt
|
||||
}
|
||||
|
||||
mappingsObject.entrySet().forEach { (key, value): Map.Entry<String, JsonElement> ->
|
||||
if (value.isJsonArray) {
|
||||
mappings[key] = context.gson.fromJson(value, ArrayList::class.java)
|
||||
return@forEach
|
||||
}
|
||||
if (value.isJsonObject) {
|
||||
mappings[key] = context.gson.fromJson(value, ConcurrentHashMap::class.java)
|
||||
return@forEach
|
||||
}
|
||||
mappings[key] = value.asString
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private fun refresh() {
|
||||
val mapper = Mapper(*mappers)
|
||||
|
||||
runCatching {
|
||||
mapper.loadApk(context.androidContext.packageManager.getApplicationInfo(
|
||||
Constants.SNAPCHAT_PACKAGE_NAME,
|
||||
0
|
||||
).sourceDir)
|
||||
}.onFailure {
|
||||
throw Exception("Failed to load APK", it)
|
||||
}
|
||||
|
||||
measureTimeMillis {
|
||||
val result = mapper.start().apply {
|
||||
addProperty("snap_build_number", snapBuildNumber)
|
||||
}
|
||||
context.bridgeClient.writeFile(BridgeFileType.MAPPINGS, result.toString().toByteArray())
|
||||
}.also {
|
||||
Logger.xposedLog("Generated mappings in $it ms")
|
||||
}
|
||||
}
|
||||
|
||||
fun getMappedObject(key: String): Any {
|
||||
if (mappings.containsKey(key)) {
|
||||
return mappings[key]!!
|
||||
}
|
||||
throw Exception("No mapping found for $key")
|
||||
}
|
||||
|
||||
fun getMappedObjectNullable(key: String): Any? {
|
||||
return mappings[key]
|
||||
}
|
||||
|
||||
fun getMappedClass(className: String): Class<*> {
|
||||
return context.androidContext.classLoader.loadClass(getMappedObject(className) as String)
|
||||
}
|
||||
|
||||
fun getMappedClass(key: String, subKey: String): Class<*> {
|
||||
return context.androidContext.classLoader.loadClass(getMappedValue(key, subKey))
|
||||
}
|
||||
|
||||
fun getMappedValue(key: String): String {
|
||||
return getMappedObject(key) as String
|
||||
}
|
||||
|
||||
fun <T : Any> getMappedList(key: String): List<T> {
|
||||
return listOf(getMappedObject(key) as List<T>).flatten()
|
||||
}
|
||||
|
||||
fun getMappedValue(key: String, subKey: String): String {
|
||||
return getMappedMap(key)[subKey] as String
|
||||
}
|
||||
|
||||
fun getMappedMap(key: String): Map<String, *> {
|
||||
return getMappedObject(key) as Map<String, *>
|
||||
}
|
||||
}
|
@ -204,7 +204,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
|
||||
//mapped conversation fetch (may not work with legacy sc versions)
|
||||
messaging.lastFetchGroupConversationUUID?.let {
|
||||
context.database.getFriendFeedInfoByConversationId(it.toString())?.let { friendFeedInfo ->
|
||||
context.database.getFeedEntryByConversationId(it.toString())?.let { friendFeedInfo ->
|
||||
val participantSize = friendFeedInfo.participantsSize
|
||||
return it.toString() to if (participantSize == 1) focusedConversationTargetUser else null
|
||||
}
|
||||
@ -280,7 +280,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
}
|
||||
|
||||
run {
|
||||
val userId = context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId ?: return@run
|
||||
val userId = context.database.getFeedEntryByConversationId(conversationId)?.friendUserId ?: return@run
|
||||
if (friendFeedMenuOptions.contains("auto_download_blacklist")) {
|
||||
createToggleFeature(viewConsumer,
|
||||
"friend_menu_option.auto_download_blacklist",
|
||||
@ -340,7 +340,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
|
||||
if (friendFeedMenuOptions.contains("auto_download_blacklist")) {
|
||||
run {
|
||||
val userId =
|
||||
context.database.getFriendFeedInfoByConversationId(conversationId)?.friendUserId
|
||||
context.database.getFeedEntryByConversationId(conversationId)?.friendUserId
|
||||
?: return@run
|
||||
createActionButton(
|
||||
"\u2B07\uFE0F",
|
||||
|
@ -1,5 +0,0 @@
|
||||
package me.rhunk.snapenhance.util
|
||||
|
||||
import android.content.Intent
|
||||
|
||||
typealias ActivityResultCallback = (requestCode: Int, resultCode: Int, data: Intent?) -> Unit
|
@ -0,0 +1,22 @@
|
||||
package me.rhunk.snapenhance.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)
|
||||
}
|
||||
}
|
@ -18,9 +18,9 @@ import me.rhunk.snapenhance.data.FileType
|
||||
import me.rhunk.snapenhance.data.MediaReferenceType
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.Message
|
||||
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
|
||||
import me.rhunk.snapenhance.database.objects.FriendFeedInfo
|
||||
import me.rhunk.snapenhance.database.objects.FriendFeedEntry
|
||||
import me.rhunk.snapenhance.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.util.getApplicationInfoCompat
|
||||
import me.rhunk.snapenhance.util.ktx.getApplicationInfoCompat
|
||||
import me.rhunk.snapenhance.util.protobuf.ProtoReader
|
||||
import me.rhunk.snapenhance.util.snap.EncryptionHelper
|
||||
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
|
||||
@ -50,7 +50,7 @@ enum class ExportFormat(
|
||||
class MessageExporter(
|
||||
private val context: ModContext,
|
||||
private val outputFile: File,
|
||||
private val friendFeedInfo: FriendFeedInfo,
|
||||
private val friendFeedEntry: FriendFeedEntry,
|
||||
private val mediaToDownload: List<ContentType>? = null,
|
||||
private val printLog: (String) -> Unit = {},
|
||||
) {
|
||||
@ -59,13 +59,13 @@ class MessageExporter(
|
||||
|
||||
fun readMessages(messages: List<Message>) {
|
||||
conversationParticipants =
|
||||
context.database.getConversationParticipants(friendFeedInfo.key!!)
|
||||
context.database.getConversationParticipants(friendFeedEntry.key!!)
|
||||
?.mapNotNull {
|
||||
context.database.getFriendInfo(it)
|
||||
}?.associateBy { it.userId!! } ?: emptyMap()
|
||||
|
||||
if (conversationParticipants.isEmpty())
|
||||
throw Throwable("Failed to get conversation participants for ${friendFeedInfo.key}")
|
||||
throw Throwable("Failed to get conversation participants for ${friendFeedEntry.key}")
|
||||
|
||||
this.messages = messages.sortedBy { it.orderKey }
|
||||
}
|
||||
@ -78,8 +78,8 @@ class MessageExporter(
|
||||
|
||||
private fun exportText(output: OutputStream) {
|
||||
val writer = output.bufferedWriter()
|
||||
writer.write("Conversation key: ${friendFeedInfo.key}\n")
|
||||
writer.write("Conversation Name: ${friendFeedInfo.feedDisplayName}\n")
|
||||
writer.write("Conversation key: ${friendFeedEntry.key}\n")
|
||||
writer.write("Conversation Name: ${friendFeedEntry.feedDisplayName}\n")
|
||||
writer.write("Participants:\n")
|
||||
conversationParticipants.forEach { (userId, friendInfo) ->
|
||||
writer.write(" $userId: ${friendInfo.displayName}\n")
|
||||
@ -233,8 +233,8 @@ class MessageExporter(
|
||||
|
||||
private fun exportJson(output: OutputStream) {
|
||||
val rootObject = JsonObject().apply {
|
||||
addProperty("conversationId", friendFeedInfo.key)
|
||||
addProperty("conversationName", friendFeedInfo.feedDisplayName)
|
||||
addProperty("conversationId", friendFeedEntry.key)
|
||||
addProperty("conversationName", friendFeedEntry.feedDisplayName)
|
||||
|
||||
var index = 0
|
||||
val participants = mutableMapOf<String, Int>()
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.util
|
||||
package me.rhunk.snapenhance.util.ktx
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.PackageManager.ApplicationInfoFlags
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.util
|
||||
package me.rhunk.snapenhance.util.ktx
|
||||
|
||||
import android.database.Cursor
|
||||
|
@ -1,4 +1,4 @@
|
||||
package me.rhunk.snapenhance.util
|
||||
package me.rhunk.snapenhance.util.ktx
|
||||
|
||||
import de.robv.android.xposed.XposedHelpers
|
||||
|
@ -0,0 +1,24 @@
|
||||
package me.rhunk.snapenhance.util.snap
|
||||
|
||||
import android.content.Intent
|
||||
import me.rhunk.snapenhance.Constants
|
||||
|
||||
object SnapWidgetBroadcastReceiverHelper {
|
||||
private const val ACTION_WIDGET_UPDATE = "com.snap.android.WIDGET_APP_START_UPDATE_ACTION"
|
||||
const val CLASS_NAME = "com.snap.widgets.core.BestFriendsWidgetProvider"
|
||||
|
||||
fun create(targetAction: String, callback: Intent.() -> Unit): Intent {
|
||||
with(Intent()) {
|
||||
callback(this)
|
||||
action = ACTION_WIDGET_UPDATE
|
||||
putExtra(":)", true)
|
||||
putExtra("action", targetAction)
|
||||
setClassName(Constants.SNAPCHAT_PACKAGE_NAME, CLASS_NAME)
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
fun isIncomingIntentValid(intent: Intent): Boolean {
|
||||
return intent.action == ACTION_WIDGET_UPDATE && intent.getBooleanExtra(":)", false)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user