feat: log system

- debug actions
- move packages to core
This commit is contained in:
rhunk 2023-08-31 00:59:30 +02:00
parent 6b9e44700d
commit 61da95f41b
83 changed files with 1118 additions and 515 deletions

View File

@ -0,0 +1,189 @@
package me.rhunk.snapenhance
import android.content.SharedPreferences
import android.util.Log
import java.io.File
import java.io.OutputStream
import java.io.RandomAccessFile
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry
import java.util.zip.ZipOutputStream
import kotlin.time.Duration.Companion.hours
class LogLine(
val logLevel: LogLevel,
val dateTime: String,
val tag: String,
val message: String
) {
companion object {
fun fromString(line: String) = runCatching {
val parts = line.trimEnd().split("/")
if (parts.size != 4) return@runCatching null
LogLine(
LogLevel.fromLetter(parts[0]) ?: return@runCatching null,
parts[1],
parts[2],
parts[3]
)
}.getOrNull()
}
override fun toString(): String {
return "${logLevel.letter}/$dateTime/$tag/$message"
}
}
class LogReader(
logFile: File
) {
private val randomAccessFile = RandomAccessFile(logFile, "r")
private var startLineIndexes = mutableListOf<Long>()
var lineCount = queryLineCount()
fun incrementLineCount() {
randomAccessFile.seek(randomAccessFile.length())
startLineIndexes.add(randomAccessFile.filePointer)
lineCount++
}
private fun queryLineCount(): Int {
randomAccessFile.seek(0)
var lines = 0
var lastIndex: Long
while (true) {
lastIndex = randomAccessFile.filePointer
randomAccessFile.readLine() ?: break
startLineIndexes.add(lastIndex)
lines++
}
return lines
}
private fun getLine(index: Int): String? {
if (index <= 0 || index > lineCount) return null
randomAccessFile.seek(startLineIndexes[index])
return randomAccessFile.readLine()
}
fun getLogLine(index: Int): LogLine? {
return getLine(index)?.let { LogLine.fromString(it) }
}
}
class LogManager(
remoteSideContext: RemoteSideContext
) {
companion object {
private const val TAG = "SnapEnhanceManager"
private val LOG_LIFETIME = 24.hours
}
var lineAddListener = { _: LogLine -> }
private val logFolder = File(remoteSideContext.androidContext.cacheDir, "logs")
private val preferences: SharedPreferences
private var logFile: File
init {
if (!logFolder.exists()) {
logFolder.mkdirs()
}
preferences = remoteSideContext.androidContext.getSharedPreferences("logger", 0)
logFile = preferences.getString("log_file", null)?.let { File(it) }?.takeIf { it.exists() } ?: run {
newLogFile()
logFile
}
if (System.currentTimeMillis() - preferences.getLong("last_created", 0) > LOG_LIFETIME.inWholeMilliseconds) {
newLogFile()
}
}
private fun getCurrentDateTime(pathSafe: Boolean = false): String {
return DateTimeFormatter.ofPattern(if (pathSafe) "yyyy-MM-dd_HH-mm-ss" else "yyyy-MM-dd HH:mm:ss").format(
java.time.LocalDateTime.now()
)
}
private fun newLogFile() {
val currentTime = System.currentTimeMillis()
logFile = File(logFolder, "snapenhance_${getCurrentDateTime(pathSafe = true)}.log").also {
it.createNewFile()
}
preferences.edit().putString("log_file", logFile.absolutePath).putLong("last_created", currentTime).apply()
}
fun clearLogs() {
logFile.delete()
newLogFile()
}
fun getLogFile() = logFile
fun exportLogsToZip(outputStream: OutputStream) {
val zipOutputStream = ZipOutputStream(outputStream)
//add logFolder to zip
logFolder.walk().forEach {
if (it.isFile) {
zipOutputStream.putNextEntry(ZipEntry(it.name))
it.inputStream().copyTo(zipOutputStream)
zipOutputStream.closeEntry()
}
}
//add device info to zip
zipOutputStream.putNextEntry(ZipEntry("device_info.txt"))
zipOutputStream.close()
}
fun newReader(onAddLine: (LogLine) -> Unit) = LogReader(logFile).also {
lineAddListener = { line -> it.incrementLineCount(); onAddLine(line) }
}
fun debug(message: Any?, tag: String = TAG) {
internalLog(tag, LogLevel.DEBUG, message)
}
fun error(message: Any?, tag: String = TAG) {
internalLog(tag, LogLevel.ERROR, message)
}
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
internalLog(tag, LogLevel.ERROR, message)
internalLog(tag, LogLevel.ERROR, throwable)
}
fun info(message: Any?, tag: String = TAG) {
internalLog(tag, LogLevel.INFO, message)
}
fun verbose(message: Any?, tag: String = TAG) {
internalLog(tag, LogLevel.VERBOSE, message)
}
fun warn(message: Any?, tag: String = TAG) {
internalLog(tag, LogLevel.WARN, message)
}
fun assert(message: Any?, tag: String = TAG) {
internalLog(tag, LogLevel.ASSERT, message)
}
fun internalLog(tag: String, logLevel: LogLevel, message: Any?) {
runCatching {
val line = LogLine(logLevel, getCurrentDateTime(), tag, message.toString())
logFile.appendText("$line\n", Charsets.UTF_8)
lineAddListener(line)
Log.println(logLevel.priority, tag, message.toString())
}.onFailure {
Log.println(Log.ERROR, tag, "Failed to log message: $message")
Log.println(Log.ERROR, tag, it.toString())
}
}
}

View File

@ -3,9 +3,12 @@ package me.rhunk.snapenhance
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.core.app.CoreComponentFactory
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import coil.ImageLoader import coil.ImageLoader
import coil.decode.VideoFrameDecoder import coil.decode.VideoFrameDecoder
@ -13,18 +16,25 @@ import coil.disk.DiskCache
import coil.memory.MemoryCache import coil.memory.MemoryCache
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import me.rhunk.snapenhance.bridge.BridgeService import me.rhunk.snapenhance.bridge.BridgeService
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.core.BuildConfig
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.core.bridge.wrapper.MappingsWrapper
import me.rhunk.snapenhance.core.config.ModConfig import me.rhunk.snapenhance.core.config.ModConfig
import me.rhunk.snapenhance.download.DownloadTaskManager import me.rhunk.snapenhance.core.download.DownloadTaskManager
import me.rhunk.snapenhance.messaging.ModDatabase import me.rhunk.snapenhance.messaging.ModDatabase
import me.rhunk.snapenhance.messaging.StreaksReminder import me.rhunk.snapenhance.messaging.StreaksReminder
import me.rhunk.snapenhance.ui.manager.MainActivity
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
import me.rhunk.snapenhance.ui.manager.data.ModMappingsInfo import me.rhunk.snapenhance.ui.manager.data.ModInfo
import me.rhunk.snapenhance.ui.manager.data.PlatformInfo
import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo import me.rhunk.snapenhance.ui.manager.data.SnapchatAppInfo
import me.rhunk.snapenhance.ui.setup.Requirements import me.rhunk.snapenhance.ui.setup.Requirements
import me.rhunk.snapenhance.ui.setup.SetupActivity import me.rhunk.snapenhance.ui.setup.SetupActivity
import java.io.ByteArrayInputStream
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
class RemoteSideContext( class RemoteSideContext(
val androidContext: Context val androidContext: Context
@ -42,6 +52,7 @@ class RemoteSideContext(
val downloadTaskManager = DownloadTaskManager() val downloadTaskManager = DownloadTaskManager()
val modDatabase = ModDatabase(this) val modDatabase = ModDatabase(this)
val streaksReminder = StreaksReminder(this) val streaksReminder = StreaksReminder(this)
val log = LogManager(this)
//used to load bitmoji selfies and download previews //used to load bitmoji selfies and download previews
val imageLoader by lazy { val imageLoader by lazy {
@ -76,37 +87,57 @@ class RemoteSideContext(
modDatabase.init() modDatabase.init()
streaksReminder.init() streaksReminder.init()
}.onFailure { }.onFailure {
Logger.error("Failed to load RemoteSideContext", it) log.error("Failed to load RemoteSideContext", it)
} }
} }
fun getInstallationSummary() = InstallationSummary( val installationSummary by lazy {
snapchatInfo = mappings.getSnapchatPackageInfo()?.let { InstallationSummary(
SnapchatAppInfo( snapchatInfo = mappings.getSnapchatPackageInfo()?.let {
version = it.versionName, SnapchatAppInfo(
versionCode = it.longVersionCode packageName = it.packageName,
version = it.versionName,
versionCode = it.longVersionCode,
isLSPatched = it.applicationInfo.appComponentFactory != CoreComponentFactory::class.java.name,
isSplitApk = it.splitNames.isNotEmpty()
)
},
modInfo = ModInfo(
loaderPackageName = MainActivity::class.java.`package`?.name ?: "unknown",
buildPackageName = BuildConfig.APPLICATION_ID,
buildVersion = BuildConfig.VERSION_NAME,
buildVersionCode = BuildConfig.VERSION_CODE.toLong(),
buildIssuer = androidContext.packageManager.getPackageInfo(BuildConfig.APPLICATION_ID, PackageManager.GET_SIGNING_CERTIFICATES)
?.signingInfo?.apkContentsSigners?.firstOrNull()?.let {
val certFactory = CertificateFactory.getInstance("X509")
val cert = certFactory.generateCertificate(ByteArrayInputStream(it.toByteArray())) as X509Certificate
cert.issuerDN.toString()
} ?: throw Exception("Failed to get certificate info"),
isDebugBuild = BuildConfig.DEBUG,
mappingVersion = mappings.getGeneratedBuildNumber(),
mappingsOutdated = mappings.isMappingsOutdated()
),
platformInfo = PlatformInfo(
device = Build.DEVICE,
buildFingerprint = Build.FINGERPRINT,
androidVersion = Build.VERSION.RELEASE,
systemAbi = Build.SUPPORTED_ABIS.firstOrNull() ?: "unknown"
) )
}, )
mappingsInfo = if (mappings.isMappingsLoaded()) { }
ModMappingsInfo(
generatedSnapchatVersion = mappings.getGeneratedBuildNumber(),
isOutdated = mappings.isMappingsOutdated()
)
} else null
)
fun longToast(message: Any) { fun longToast(message: Any) {
androidContext.mainExecutor.execute { androidContext.mainExecutor.execute {
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show() Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
} }
Logger.debug(message.toString()) log.debug(message.toString())
} }
fun shortToast(message: Any) { fun shortToast(message: Any) {
androidContext.mainExecutor.execute { androidContext.mainExecutor.execute {
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show() Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
} }
Logger.debug(message.toString()) log.debug(message.toString())
} }
fun checkForRequirements(overrideRequirements: Int? = null): Boolean { fun checkForRequirements(overrideRequirements: Int? = null): Boolean {

View File

@ -3,16 +3,16 @@ package me.rhunk.snapenhance.bridge
import android.app.Service import android.app.Service
import android.content.Intent import android.content.Intent
import android.os.IBinder import android.os.IBinder
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.LogLevel
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.SharedContextHolder import me.rhunk.snapenhance.SharedContextHolder
import me.rhunk.snapenhance.bridge.types.BridgeFileType import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import me.rhunk.snapenhance.bridge.types.FileActionType import me.rhunk.snapenhance.core.bridge.types.FileActionType
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.bridge.wrapper.MessageLoggerWrapper import me.rhunk.snapenhance.core.bridge.wrapper.MessageLoggerWrapper
import me.rhunk.snapenhance.core.database.objects.FriendInfo
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
import me.rhunk.snapenhance.database.objects.FriendInfo
import me.rhunk.snapenhance.download.DownloadProcessor import me.rhunk.snapenhance.download.DownloadProcessor
import me.rhunk.snapenhance.util.SerializableDataObject import me.rhunk.snapenhance.util.SerializableDataObject
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -36,7 +36,7 @@ class BridgeService : Service() {
fun triggerFriendSync(friendId: String) { fun triggerFriendSync(friendId: String) {
val syncedFriend = syncCallback.syncFriend(friendId) val syncedFriend = syncCallback.syncFriend(friendId)
if (syncedFriend == null) { if (syncedFriend == null) {
Logger.error("Failed to sync friend $friendId") remoteSideContext.log.error("Failed to sync friend $friendId")
return return
} }
SerializableDataObject.fromJson<FriendInfo>(syncedFriend).let { SerializableDataObject.fromJson<FriendInfo>(syncedFriend).let {
@ -47,7 +47,7 @@ class BridgeService : Service() {
fun triggerGroupSync(groupId: String) { fun triggerGroupSync(groupId: String) {
val syncedGroup = syncCallback.syncGroup(groupId) val syncedGroup = syncCallback.syncGroup(groupId)
if (syncedGroup == null) { if (syncedGroup == null) {
Logger.error("Failed to sync group $groupId") remoteSideContext.log.error("Failed to sync group $groupId")
return return
} }
SerializableDataObject.fromJson<MessagingGroupInfo>(syncedGroup).let { SerializableDataObject.fromJson<MessagingGroupInfo>(syncedGroup).let {
@ -56,10 +56,12 @@ class BridgeService : Service() {
} }
inner class BridgeBinder : BridgeInterface.Stub() { inner class BridgeBinder : BridgeInterface.Stub() {
override fun broadcastLog(tag: String, level: String, message: String) {
remoteSideContext.log.internalLog(tag, LogLevel.fromShortName(level) ?: LogLevel.INFO, message)
}
override fun fileOperation(action: Int, fileType: Int, content: ByteArray?): ByteArray { override fun fileOperation(action: Int, fileType: Int, content: ByteArray?): ByteArray {
val resolvedFile by lazy { val resolvedFile = BridgeFileType.fromValue(fileType)?.resolve(this@BridgeService)
BridgeFileType.fromValue(fileType)?.resolve(this@BridgeService)
}
return when (FileActionType.values()[action]) { return when (FileActionType.values()[action]) {
FileActionType.CREATE_AND_READ -> { FileActionType.CREATE_AND_READ -> {
@ -108,8 +110,6 @@ class BridgeService : Service() {
override fun deleteMessageLoggerMessage(conversationId: String, id: Long) = override fun deleteMessageLoggerMessage(conversationId: String, id: Long) =
messageLoggerWrapper.deleteMessage(conversationId, id) messageLoggerWrapper.deleteMessage(conversationId, id)
override fun clearMessageLogger() = messageLoggerWrapper.clearMessages()
override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir override fun getApplicationApkPath(): String = applicationInfo.publicSourceDir
override fun fetchLocales(userLocale: String) = override fun fetchLocales(userLocale: String) =
@ -133,25 +133,24 @@ class BridgeService : Service() {
} }
override fun sync(callback: SyncCallback) { override fun sync(callback: SyncCallback) {
Logger.debug("Syncing remote")
syncCallback = callback syncCallback = callback
measureTimeMillis { measureTimeMillis {
remoteSideContext.modDatabase.getFriends().map { it.userId } .forEach { friendId -> remoteSideContext.modDatabase.getFriends().map { it.userId } .forEach { friendId ->
runCatching { runCatching {
triggerFriendSync(friendId) triggerFriendSync(friendId)
}.onFailure { }.onFailure {
Logger.error("Failed to sync friend $friendId", it) remoteSideContext.log.error("Failed to sync friend $friendId", it)
} }
} }
remoteSideContext.modDatabase.getGroups().map { it.conversationId }.forEach { groupId -> remoteSideContext.modDatabase.getGroups().map { it.conversationId }.forEach { groupId ->
runCatching { runCatching {
triggerGroupSync(groupId) triggerGroupSync(groupId)
}.onFailure { }.onFailure {
Logger.error("Failed to sync group $groupId", it) remoteSideContext.log.error("Failed to sync group $groupId", it)
} }
} }
}.also { }.also {
Logger.debug("Syncing remote took $it ms") remoteSideContext.log.verbose("Syncing remote took $it ms")
} }
} }
@ -159,7 +158,7 @@ class BridgeService : Service() {
groups: List<String>, groups: List<String>,
friends: List<String> friends: List<String>
) { ) {
Logger.debug("Received ${groups.size} groups and ${friends.size} friends") remoteSideContext.log.verbose("Received ${groups.size} groups and ${friends.size} friends")
remoteSideContext.modDatabase.receiveMessagingDataCallback( remoteSideContext.modDatabase.receiveMessagingDataCallback(
friends.map { SerializableDataObject.fromJson<MessagingFriendInfo>(it) }, friends.map { SerializableDataObject.fromJson<MessagingFriendInfo>(it) },
groups.map { SerializableDataObject.fromJson<MessagingGroupInfo>(it) } groups.map { SerializableDataObject.fromJson<MessagingGroupInfo>(it) }

View File

@ -19,14 +19,15 @@ import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.bridge.DownloadCallback import me.rhunk.snapenhance.bridge.DownloadCallback
import me.rhunk.snapenhance.core.download.DownloadManagerClient
import me.rhunk.snapenhance.data.FileType import me.rhunk.snapenhance.data.FileType
import me.rhunk.snapenhance.download.data.DownloadMediaType import me.rhunk.snapenhance.core.download.data.DownloadMediaType
import me.rhunk.snapenhance.download.data.DownloadMetadata import me.rhunk.snapenhance.core.download.data.DownloadMetadata
import me.rhunk.snapenhance.download.data.DownloadObject import me.rhunk.snapenhance.core.download.data.DownloadObject
import me.rhunk.snapenhance.download.data.DownloadRequest import me.rhunk.snapenhance.core.download.data.DownloadRequest
import me.rhunk.snapenhance.download.data.DownloadStage import me.rhunk.snapenhance.core.download.data.DownloadStage
import me.rhunk.snapenhance.download.data.InputMedia import me.rhunk.snapenhance.core.download.data.InputMedia
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair import me.rhunk.snapenhance.core.download.data.MediaEncryptionKeyPair
import me.rhunk.snapenhance.util.download.RemoteMediaResolver import me.rhunk.snapenhance.util.download.RemoteMediaResolver
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
import java.io.File import java.io.File
@ -178,14 +179,14 @@ class DownloadProcessor (
mediaScanIntent.setData(outputFile.uri) mediaScanIntent.setData(outputFile.uri)
remoteSideContext.androidContext.sendBroadcast(mediaScanIntent) remoteSideContext.androidContext.sendBroadcast(mediaScanIntent)
}.onFailure { }.onFailure {
Logger.error("Failed to scan media file", it) remoteSideContext.log.error("Failed to scan media file", it)
callbackOnFailure(translation.format("failed_gallery_toast", "error" to it.toString()), it.message) callbackOnFailure(translation.format("failed_gallery_toast", "error" to it.toString()), it.message)
} }
Logger.debug("download complete") remoteSideContext.log.verbose("download complete")
callbackOnSuccess(fileName) callbackOnSuccess(fileName)
}.onFailure { exception -> }.onFailure { exception ->
Logger.error(exception) remoteSideContext.log.error("Failed to save media to gallery", exception)
callbackOnFailure(translation.format("failed_gallery_toast", "error" to exception.toString()), exception.message) callbackOnFailure(translation.format("failed_gallery_toast", "error" to exception.toString()), exception.message)
downloadObject.downloadStage = DownloadStage.FAILED downloadObject.downloadStage = DownloadStage.FAILED
} }
@ -284,7 +285,7 @@ class DownloadProcessor (
saveMediaToGallery(outputFile, downloadObjectObject) saveMediaToGallery(outputFile, downloadObjectObject)
}.onFailure { exception -> }.onFailure { exception ->
if (coroutineContext.job.isCancelled) return@onFailure if (coroutineContext.job.isCancelled) return@onFailure
Logger.error(exception) remoteSideContext.log.error("Failed to download dash media", exception)
callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message) callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message)
downloadObjectObject.downloadStage = DownloadStage.FAILED downloadObjectObject.downloadStage = DownloadStage.FAILED
} }
@ -333,7 +334,7 @@ class DownloadProcessor (
val downloadedMedias = downloadInputMedias(downloadRequest).map { val downloadedMedias = downloadInputMedias(downloadRequest).map {
it.key to DownloadedFile(it.value, FileType.fromFile(it.value)) it.key to DownloadedFile(it.value, FileType.fromFile(it.value))
}.toMap().toMutableMap() }.toMap().toMutableMap()
Logger.debug("downloaded ${downloadedMedias.size} medias") remoteSideContext.log.verbose("downloaded ${downloadedMedias.size} medias")
var shouldMergeOverlay = downloadRequest.shouldMergeOverlay var shouldMergeOverlay = downloadRequest.shouldMergeOverlay
@ -376,7 +377,7 @@ class DownloadProcessor (
saveMediaToGallery(mergedOverlay, downloadObjectObject) saveMediaToGallery(mergedOverlay, downloadObjectObject)
}.onFailure { exception -> }.onFailure { exception ->
if (coroutineContext.job.isCancelled) return@onFailure if (coroutineContext.job.isCancelled) return@onFailure
Logger.error(exception) remoteSideContext.log.error("Failed to merge overlay", exception)
callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message) callbackOnFailure(translation.format("failed_processing_toast", "error" to exception.toString()), exception.message)
downloadObjectObject.downloadStage = DownloadStage.MERGE_FAILED downloadObjectObject.downloadStage = DownloadStage.MERGE_FAILED
} }
@ -390,7 +391,7 @@ class DownloadProcessor (
downloadRemoteMedia(downloadObjectObject, downloadedMedias, downloadRequest) downloadRemoteMedia(downloadObjectObject, downloadedMedias, downloadRequest)
}.onFailure { exception -> }.onFailure { exception ->
downloadObjectObject.downloadStage = DownloadStage.FAILED downloadObjectObject.downloadStage = DownloadStage.FAILED
Logger.error(exception) remoteSideContext.log.error("Failed to download media", exception)
callbackOnFailure(translation["failed_generic_toast"], exception.message) callbackOnFailure(translation["failed_generic_toast"], exception.message)
} }
} }

View File

@ -1,13 +1,12 @@
package me.rhunk.snapenhance.messaging package me.rhunk.snapenhance.messaging
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.core.database.objects.FriendInfo
import me.rhunk.snapenhance.core.messaging.FriendStreaks import me.rhunk.snapenhance.core.messaging.FriendStreaks
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
import me.rhunk.snapenhance.core.messaging.MessagingRuleType import me.rhunk.snapenhance.core.messaging.MessagingRuleType
import me.rhunk.snapenhance.database.objects.FriendInfo
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
import me.rhunk.snapenhance.util.ktx.getInteger import me.rhunk.snapenhance.util.ktx.getInteger
import me.rhunk.snapenhance.util.ktx.getLongOrNull import me.rhunk.snapenhance.util.ktx.getLongOrNull
@ -28,7 +27,7 @@ class ModDatabase(
runCatching { runCatching {
block() block()
}.onFailure { }.onFailure {
Logger.error("Failed to execute async block", it) context.log.error("Failed to execute async block", it)
} }
} }
} }
@ -103,7 +102,7 @@ class ModDatabase(
selfieId = cursor.getStringOrNull("selfieId") selfieId = cursor.getStringOrNull("selfieId")
)) ))
}.onFailure { }.onFailure {
Logger.error("Failed to parse friend", it) context.log.error("Failed to parse friend", it)
} }
} }
friends friends

View File

@ -13,7 +13,7 @@ import androidx.navigation.NavController
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.ui.manager.sections.HomeSection import me.rhunk.snapenhance.ui.manager.sections.home.HomeSection
import me.rhunk.snapenhance.ui.manager.sections.NotImplemented import me.rhunk.snapenhance.ui.manager.sections.NotImplemented
import me.rhunk.snapenhance.ui.manager.sections.downloads.DownloadsSection import me.rhunk.snapenhance.ui.manager.sections.downloads.DownloadsSection
import me.rhunk.snapenhance.ui.manager.sections.features.FeaturesSection import me.rhunk.snapenhance.ui.manager.sections.features.FeaturesSection
@ -64,6 +64,8 @@ open class Section {
lateinit var context: RemoteSideContext lateinit var context: RemoteSideContext
lateinit var navController: NavController lateinit var navController: NavController
val currentRoute get() = navController.currentBackStackEntry?.destination?.route
open fun init() {} open fun init() {}
open fun onResumed() {} open fun onResumed() {}

View File

@ -2,16 +2,33 @@ package me.rhunk.snapenhance.ui.manager.data
data class SnapchatAppInfo( data class SnapchatAppInfo(
val packageName: String,
val version: String, val version: String,
val versionCode: Long val versionCode: Long,
val isLSPatched: Boolean,
val isSplitApk: Boolean,
) )
data class ModMappingsInfo( data class ModInfo(
val generatedSnapchatVersion: Long, val loaderPackageName: String,
val isOutdated: Boolean val buildPackageName: String,
val buildVersion: String,
val buildVersionCode: Long,
val buildIssuer: String,
val isDebugBuild: Boolean,
val mappingVersion: Long?,
val mappingsOutdated: Boolean?,
)
data class PlatformInfo(
val device: String,
val buildFingerprint: String,
val androidVersion: String,
val systemAbi: String,
) )
data class InstallationSummary( data class InstallationSummary(
val platformInfo: PlatformInfo,
val snapchatInfo: SnapchatAppInfo?, val snapchatInfo: SnapchatAppInfo?,
val mappingsInfo: ModMappingsInfo? val modInfo: ModInfo?,
) )

View File

@ -1,147 +0,0 @@
package me.rhunk.snapenhance.ui.manager.sections
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Map
import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Button
import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import me.rhunk.snapenhance.ui.manager.Section
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
import me.rhunk.snapenhance.ui.setup.Requirements
import java.util.Locale
class HomeSection : Section() {
companion object {
val cardMargin = 10.dp
}
private val installationSummary = mutableStateOf(null as InstallationSummary?)
private val userLocale = mutableStateOf(null as String?)
@Composable
private fun SummaryCards(installationSummary: InstallationSummary) {
//installation summary
OutlinedCard(
modifier = Modifier
.padding(all = cardMargin)
.fillMaxWidth()
) {
Column(modifier = Modifier.padding(all = 16.dp)) {
if (installationSummary.snapchatInfo != null) {
Text("Snapchat version: ${installationSummary.snapchatInfo.version}")
Text("Snapchat version code: ${installationSummary.snapchatInfo.versionCode}")
} else {
Text("Snapchat not installed/detected")
}
}
}
OutlinedCard(
modifier = Modifier
.padding(all = cardMargin)
.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(all = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
Icon(
Icons.Filled.Map,
contentDescription = "Mappings",
modifier = Modifier
.padding(end = 10.dp)
.align(Alignment.CenterVertically)
)
Text(text = if (installationSummary.mappingsInfo == null || installationSummary.mappingsInfo.isOutdated) {
"Mappings ${if (installationSummary.mappingsInfo == null) "not generated" else "outdated"}"
} else {
"Mappings version ${installationSummary.mappingsInfo.generatedSnapchatVersion}"
}, modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
)
//inline button
Button(onClick = {
context.checkForRequirements(Requirements.MAPPINGS)
}, modifier = Modifier.height(40.dp)) {
Icon(Icons.Filled.Refresh, contentDescription = "Refresh")
}
}
}
OutlinedCard(
modifier = Modifier
.padding(all = cardMargin)
.fillMaxWidth()
) {
Row(
modifier = Modifier.padding(all = 16.dp),
) {
Icon(
Icons.Filled.Language,
contentDescription = "Language",
modifier = Modifier
.padding(end = 10.dp)
.align(Alignment.CenterVertically)
)
Text(text = userLocale.value ?: "Unknown", modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
)
//inline button
Button(onClick = {
context.checkForRequirements(Requirements.LANGUAGE)
}, modifier = Modifier.height(40.dp)) {
Icon(Icons.Filled.OpenInNew, contentDescription = null)
}
}
}
}
override fun onResumed() {
if (!context.mappings.isMappingsLoaded()) {
context.mappings.init(context.androidContext)
}
installationSummary.value = context.getInstallationSummary()
userLocale.value = context.translation.loadedLocale.getDisplayName(Locale.getDefault())
}
override fun sectionTopBarName() = "SnapEnhance"
@Composable
@Preview
override fun Content() {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(ScrollState(0))
) {
Text(
text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl. Donec euismod, nisl eget ultricies ultrices, nunc nisl aliquam nunc, quis aliquam nisl nunc eu nisl.",
modifier = Modifier.padding(16.dp)
)
SummaryCards(installationSummary = installationSummary.value ?: return)
}
}
}

View File

@ -51,8 +51,8 @@ import androidx.compose.ui.unit.sp
import coil.compose.rememberAsyncImagePainter import coil.compose.rememberAsyncImagePainter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.rhunk.snapenhance.data.FileType import me.rhunk.snapenhance.data.FileType
import me.rhunk.snapenhance.download.data.DownloadObject import me.rhunk.snapenhance.core.download.data.DownloadObject
import me.rhunk.snapenhance.download.data.MediaFilter import me.rhunk.snapenhance.core.download.data.MediaFilter
import me.rhunk.snapenhance.ui.manager.Section import me.rhunk.snapenhance.ui.manager.Section
import me.rhunk.snapenhance.ui.util.BitmojiImage import me.rhunk.snapenhance.ui.util.BitmojiImage
import me.rhunk.snapenhance.ui.util.ImageRequestHelper import me.rhunk.snapenhance.ui.util.ImageRequestHelper

View File

@ -439,7 +439,7 @@ class FeaturesSection : Section() {
IconButton(onClick = { IconButton(onClick = {
showSearchBar = showSearchBar.not() showSearchBar = showSearchBar.not()
if (!showSearchBar && navController.currentBackStackEntry?.destination?.route == SEARCH_FEATURE_ROUTE) { if (!showSearchBar && currentRoute == SEARCH_FEATURE_ROUTE) {
navController.navigate(MAIN_ROUTE) navController.navigate(MAIN_ROUTE)
} }
}) { }) {

View File

@ -0,0 +1,312 @@
package me.rhunk.snapenhance.ui.manager.sections.home
import android.net.Uri
import androidx.compose.foundation.Image
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.filled.Language
import androidx.compose.material.icons.filled.Map
import androidx.compose.material.icons.filled.MoreVert
import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material.icons.filled.ReceiptLong
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedCard
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.scale
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable
import androidx.navigation.navigation
import me.rhunk.snapenhance.R
import me.rhunk.snapenhance.ui.manager.Section
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
import me.rhunk.snapenhance.ui.setup.Requirements
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
import me.rhunk.snapenhance.ui.util.saveFile
import java.util.Locale
class HomeSection : Section() {
companion object {
val cardMargin = 10.dp
const val HOME_ROOT = "home_root"
const val DEBUG_SECTION_ROUTE = "home_debug"
const val LOGS_SECTION_ROUTE = "home_logs"
}
private val installationSummary = mutableStateOf(null as InstallationSummary?)
private val userLocale = mutableStateOf(null as String?)
private val homeSubSection by lazy { HomeSubSection(context) }
private lateinit var activityLauncherHelper: ActivityLauncherHelper
override fun init() {
activityLauncherHelper = ActivityLauncherHelper(context.activity!!)
}
@Composable
private fun SummaryCardRow(icon: ImageVector? = null, title: String, action: @Composable () -> Unit) {
Row(
modifier = Modifier.padding(all = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween
) {
icon?.let {
Icon(
imageVector = it,
contentDescription = null,
modifier = Modifier
.padding(end = 10.dp)
.align(Alignment.CenterVertically)
)
}
Text(text = title, modifier = Modifier
.weight(1f)
.align(Alignment.CenterVertically)
)
Column {
action()
}
}
}
@Composable
private fun SummaryCards(installationSummary: InstallationSummary) {
OutlinedCard(
modifier = Modifier
.padding(all = cardMargin)
.fillMaxWidth()
) {
SummaryCardRow(
icon = Icons.Filled.Map,
title = if (installationSummary.modInfo == null || installationSummary.modInfo.mappingsOutdated == true) {
"Mappings ${if (installationSummary.modInfo == null) "not generated" else "outdated"}"
} else {
"Mappings version ${installationSummary.modInfo.mappingVersion}"
}
) {
Button(onClick = {
context.checkForRequirements(Requirements.MAPPINGS)
}, modifier = Modifier.height(40.dp)) {
Icon(Icons.Filled.Refresh, contentDescription = null)
}
}
SummaryCardRow(icon = Icons.Filled.Language, title = userLocale.value ?: "Unknown") {
Button(onClick = {
context.checkForRequirements(Requirements.LANGUAGE)
}, modifier = Modifier.height(40.dp)) {
Icon(Icons.Filled.OpenInNew, contentDescription = null)
}
}
}
val summaryInfo = remember {
mapOf(
"Build Issuer" to (installationSummary.modInfo?.buildIssuer ?: "Unknown"),
"Device" to installationSummary.platformInfo.device,
"Android version" to installationSummary.platformInfo.androidVersion,
"System ABI" to installationSummary.platformInfo.systemAbi,
"Build fingerprint" to installationSummary.platformInfo.buildFingerprint
)
}
Card(
modifier = Modifier
.padding(all = cardMargin)
.fillMaxWidth(),
colors = CardDefaults.cardColors(
containerColor = MaterialTheme.colorScheme.surfaceVariant,
contentColor = MaterialTheme.colorScheme.onSurfaceVariant
)
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(all = 10.dp),
) {
summaryInfo.forEach { (title, value) ->
Column(
modifier = Modifier
.fillMaxWidth()
.padding(all = 5.dp),
) {
Text(
text = title,
fontSize = 12.sp,
fontWeight = FontWeight.Light,
)
Text(
fontSize = 14.sp,
text = value,
lineHeight = 20.sp
)
}
}
}
}
}
override fun onResumed() {
if (!context.mappings.isMappingsLoaded()) {
context.mappings.init(context.androidContext)
}
installationSummary.value = context.installationSummary
userLocale.value = context.translation.loadedLocale.getDisplayName(Locale.getDefault())
}
override fun sectionTopBarName(): String {
if (currentRoute == HOME_ROOT) {
return ""
}
return context.translation["manager.routes.$currentRoute"]
}
@Composable
override fun FloatingActionButton() {
if (currentRoute == LOGS_SECTION_ROUTE) {
homeSubSection.LogsActionButtons()
}
}
@Composable
override fun TopBarActions(rowScope: RowScope) {
rowScope.apply {
when (currentRoute) {
HOME_ROOT -> {
IconButton(onClick = {
navController.navigate(LOGS_SECTION_ROUTE)
}) {
Icon(Icons.Filled.ReceiptLong, contentDescription = null)
}
IconButton(onClick = {
navController.navigate(DEBUG_SECTION_ROUTE)
}) {
Icon(Icons.Filled.BugReport, contentDescription = null)
}
}
LOGS_SECTION_ROUTE -> {
var showDropDown by remember { mutableStateOf(false) }
IconButton(onClick = {
showDropDown = true
}) {
Icon(Icons.Filled.MoreVert, contentDescription = null)
}
DropdownMenu(
expanded = showDropDown,
onDismissRequest = { showDropDown = false },
modifier = Modifier.align(Alignment.CenterVertically)
) {
DropdownMenuItem(onClick = {
context.log.clearLogs()
navController.navigate(LOGS_SECTION_ROUTE)
showDropDown = false
}, text = {
Text(text = "Clear logs")
})
DropdownMenuItem(onClick = {
val logFile = context.log.getLogFile()
activityLauncherHelper.saveFile(logFile.name, "text/plain") { uri ->
context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use {
logFile.inputStream().copyTo(it)
context.longToast("Saved logs to $uri")
}
}
showDropDown = false
}, text = {
Text(text = "Export logs")
})
}
}
}
}
}
override fun build(navGraphBuilder: NavGraphBuilder) {
navGraphBuilder.navigation(
route = enumSection.route,
startDestination = HOME_ROOT
) {
composable(HOME_ROOT) {
Content()
}
composable(LOGS_SECTION_ROUTE) {
homeSubSection.LogsSection()
}
composable(DEBUG_SECTION_ROUTE) {
homeSubSection.DebugSection()
}
}
}
@Composable
@Preview
override fun Content() {
Column(
modifier = Modifier
.verticalScroll(ScrollState(0))
) {
Column(
modifier = Modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.drawable.launcher_icon_monochrome),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.primary),
contentScale = ContentScale.FillHeight,
modifier = Modifier
.height(120.dp)
.scale(1.75f)
)
Text(
text = ("\u0065" + "\u0063" + "\u006e" + "\u0061" + "\u0068" + "\u006e" + "\u0045" + "\u0070" + "\u0061" + "\u006e" + "\u0053").reversed(),
fontSize = 30.sp,
modifier = Modifier.padding(16.dp),
)
}
Text(
text = "An xposed module that enhances the Snapchat experience",
modifier = Modifier.padding(16.dp)
)
SummaryCards(installationSummary = installationSummary.value ?: return)
}
}
}

View File

@ -0,0 +1,214 @@
package me.rhunk.snapenhance.ui.manager.sections.home
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.KeyboardDoubleArrowDown
import androidx.compose.material.icons.filled.KeyboardDoubleArrowUp
import androidx.compose.material.icons.filled.OpenInNew
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.LogReader
import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.action.EnumAction
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import me.rhunk.snapenhance.manager.impl.ActionManager
import me.rhunk.snapenhance.ui.util.AlertDialogs
class HomeSubSection(
private val context: RemoteSideContext
) {
private val dialogs by lazy { AlertDialogs(context.translation) }
private lateinit var logListState: LazyListState
@Composable
private fun RowAction(title: String, requireConfirmation: Boolean = false, action: () -> Unit) {
var confirmationDialog by remember {
mutableStateOf(false)
}
fun takeAction() {
if (requireConfirmation) {
confirmationDialog = true
} else {
action()
}
}
if (requireConfirmation && confirmationDialog) {
Dialog(onDismissRequest = { confirmationDialog = false }) {
dialogs.ConfirmDialog(title = "Are you sure?", onConfirm = {
action()
confirmationDialog = false
}, onDismiss = {
confirmationDialog = false
})
}
}
Row(
modifier = Modifier
.fillMaxWidth()
.height(65.dp)
.clickable {
takeAction()
},
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(text = title, modifier = Modifier.padding(start = 26.dp))
IconButton(onClick = { takeAction() }) {
Icon(
imageVector = Icons.Filled.OpenInNew,
contentDescription = null,
modifier = Modifier.size(24.dp)
)
}
}
}
@Composable
fun LogsSection() {
val coroutineScope = rememberCoroutineScope()
var lineCount by remember { mutableIntStateOf(0) }
var logReader by remember { mutableStateOf<LogReader?>(null) }
logListState = remember { LazyListState(0) }
Column(
modifier = Modifier
.fillMaxSize()
) {
LazyColumn(
modifier = Modifier.background(MaterialTheme.colorScheme.surfaceVariant),
state = logListState
) {
items(lineCount) { index ->
val line = logReader?.getLogLine(index) ?: return@items
Box(modifier = Modifier
.fillMaxWidth()
.background(
if (index % 2 == 0) MaterialTheme.colorScheme.surface else MaterialTheme.colorScheme.surfaceVariant
)) {
Text(text = line.message, modifier = Modifier.padding(9.dp), fontSize = 10.sp)
}
}
}
if (logReader == null) {
CircularProgressIndicator(modifier = Modifier.align(Alignment.CenterHorizontally))
}
LaunchedEffect(Unit) {
coroutineScope.launch(Dispatchers.IO) {
runCatching {
logReader = context.log.newReader {
lineCount++
}
lineCount = logReader!!.lineCount
}.onFailure {
context.longToast("Failed to read logs!")
}
}
}
}
}
@Composable
fun LogsActionButtons() {
val coroutineScope = rememberCoroutineScope()
Column(
verticalArrangement = Arrangement.spacedBy(5.dp),
) {
FilledIconButton(onClick = {
coroutineScope.launch {
logListState.scrollToItem(0)
}
}) {
Icon(Icons.Filled.KeyboardDoubleArrowUp, contentDescription = null)
}
FilledIconButton(onClick = {
coroutineScope.launch {
logListState.scrollToItem(logListState.layoutInfo.totalItemsCount - 1)
}
}) {
Icon(Icons.Filled.KeyboardDoubleArrowDown, contentDescription = null)
}
}
}
private fun launchActionIntent(action: EnumAction) {
val intent = context.androidContext.packageManager.getLaunchIntentForPackage(Constants.SNAPCHAT_PACKAGE_NAME)
intent?.putExtra(ActionManager.ACTION_PARAMETER, action.key)
context.androidContext.startActivity(intent)
}
@Composable
private fun RowTitle(title: String) {
Text(text = title, modifier = Modifier.padding(16.dp), fontSize = 20.sp, fontWeight = FontWeight.Bold)
}
@Composable
fun DebugSection() {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(ScrollState(0))
) {
RowTitle(title = "Actions")
EnumAction.values().forEach { enumAction ->
RowAction(title = context.translation["actions.${enumAction.key}"]) {
launchActionIntent(enumAction)
}
}
RowTitle(title = "Clear Files")
BridgeFileType.values().forEach { fileType ->
RowAction(title = fileType.displayName, requireConfirmation = true) {
runCatching {
fileType.resolve(context.androidContext).delete()
context.longToast("Deleted ${fileType.displayName}!")
}.onFailure {
context.longToast("Failed to delete ${fileType.displayName}!")
}
}
}
}
}
}

View File

@ -43,9 +43,8 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.RemoteSideContext import me.rhunk.snapenhance.RemoteSideContext
import me.rhunk.snapenhance.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper import me.rhunk.snapenhance.util.snap.SnapWidgetBroadcastReceiverHelper
@ -55,8 +54,8 @@ class AddFriendDialog(
private val section: SocialSection, private val section: SocialSection,
) { ) {
@Composable @Composable
private fun ListCardEntry(name: String, currentState: () -> Boolean, onState: (Boolean) -> Unit = {}) { private fun ListCardEntry(name: String, getCurrentState: () -> Boolean, onState: (Boolean) -> Unit = {}) {
var currentState by remember { mutableStateOf(currentState()) } var currentState by remember { mutableStateOf(getCurrentState()) }
Row( Row(
modifier = Modifier modifier = Modifier
@ -74,7 +73,7 @@ class AddFriendDialog(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.onGloballyPositioned { .onGloballyPositioned {
currentState = currentState() currentState = getCurrentState()
} }
) )
@ -149,7 +148,7 @@ class AddFriendDialog(
runCatching { runCatching {
context.androidContext.sendBroadcast(it) context.androidContext.sendBroadcast(it)
}.onFailure { }.onFailure {
Logger.error("Failed to send broadcast", it) context.log.error("Failed to send broadcast", it)
hasFetchError = true hasFetchError = true
} }
} }
@ -234,7 +233,7 @@ class AddFriendDialog(
val group = filteredGroups[it] val group = filteredGroups[it]
ListCardEntry( ListCardEntry(
name = group.name, name = group.name,
currentState = { context.modDatabase.getGroupInfo(group.conversationId) != null } getCurrentState = { context.modDatabase.getGroupInfo(group.conversationId) != null }
) { state -> ) { state ->
if (state) { if (state) {
context.bridgeService.triggerGroupSync(group.conversationId) context.bridgeService.triggerGroupSync(group.conversationId)
@ -261,7 +260,7 @@ class AddFriendDialog(
ListCardEntry( ListCardEntry(
name = friend.displayName?.takeIf { name -> name.isNotBlank() } ?: friend.mutableUsername, name = friend.displayName?.takeIf { name -> name.isNotBlank() } ?: friend.mutableUsername,
currentState = { context.modDatabase.getFriendInfo(friend.userId) != null } getCurrentState = { context.modDatabase.getFriendInfo(friend.userId) != null }
) { state -> ) { state ->
if (state) { if (state) {
context.bridgeService.triggerFriendSync(friend.userId) context.bridgeService.triggerFriendSync(friend.userId)

View File

@ -79,7 +79,7 @@ class SocialSection : Section() {
groupList = context.modDatabase.getGroups() groupList = context.modDatabase.getGroups()
} }
override fun canGoBack() = navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE override fun canGoBack() = currentRoute != MAIN_ROUTE
override fun build(navGraphBuilder: NavGraphBuilder) { override fun build(navGraphBuilder: NavGraphBuilder) {
navGraphBuilder.navigation(route = enumSection.route, startDestination = MAIN_ROUTE) { navGraphBuilder.navigation(route = enumSection.route, startDestination = MAIN_ROUTE) {
@ -117,7 +117,7 @@ class SocialSection : Section() {
} }
} }
if (navController.currentBackStackEntry?.destination?.route != MAIN_ROUTE) { if (currentRoute != MAIN_ROUTE) {
IconButton( IconButton(
onClick = { deleteConfirmDialog = true }, onClick = { deleteConfirmDialog = true },
) { ) {

View File

@ -44,7 +44,7 @@ class MappingsScreen : SetupScreen() {
fun tryToGenerateMappings() { fun tryToGenerateMappings() {
//check for snapchat installation //check for snapchat installation
val installationSummary = context.getInstallationSummary() val installationSummary = context.installationSummary
if (installationSummary.snapchatInfo == null) { if (installationSummary.snapchatInfo == null) {
throw Exception(context.translation["setup.mappings.generate_failure_no_snapchat"]) throw Exception(context.translation["setup.mappings.generate_failure_no_snapchat"])
} }
@ -69,7 +69,7 @@ class MappingsScreen : SetupScreen() {
}.onFailure { }.onFailure {
isGenerating = false isGenerating = false
infoText = context.translation["setup.mappings.generate_failure"] + "\n\n" + it.message infoText = context.translation["setup.mappings.generate_failure"] + "\n\n" + it.message
Logger.error("Failed to generate mappings", it) context.log.error("Failed to generate mappings", it)
} }
} }
}) { }) {

View File

@ -25,7 +25,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.Dialog
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
import me.rhunk.snapenhance.ui.util.ObservableMutableState import me.rhunk.snapenhance.ui.util.ObservableMutableState
import java.util.Locale import java.util.Locale

View File

@ -7,7 +7,6 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.ui.setup.screens.SetupScreen import me.rhunk.snapenhance.ui.setup.screens.SetupScreen
import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper import me.rhunk.snapenhance.ui.util.ActivityLauncherHelper
import me.rhunk.snapenhance.ui.util.ObservableMutableState import me.rhunk.snapenhance.ui.util.ObservableMutableState
@ -23,7 +22,6 @@ class SaveFolderScreen : SetupScreen() {
saveFolder = ObservableMutableState( saveFolder = ObservableMutableState(
defaultValue = "", defaultValue = "",
onChange = { _, newValue -> onChange = { _, newValue ->
Logger.debug(newValue)
if (newValue.isNotBlank()) { if (newValue.isNotBlank()) {
context.config.root.downloader.saveFolder.set(newValue) context.config.root.downloader.saveFolder.set(newValue)
context.config.writeConfig() context.config.writeConfig()

View File

@ -16,7 +16,7 @@ class ActivityLauncherHelper(
runCatching { runCatching {
callback?.let { it(result.data!!) } callback?.let { it(result.data!!) }
}.onFailure { }.onFailure {
Logger.error("Failed to process activity result", it) Logger.directError("Failed to process activity result", it)
} }
} }
callback = null callback = null

View File

@ -33,7 +33,7 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.core.config.DataProcessors import me.rhunk.snapenhance.core.config.DataProcessors
import me.rhunk.snapenhance.core.config.PropertyPair import me.rhunk.snapenhance.core.config.PropertyPair

View File

@ -5,53 +5,47 @@ import me.rhunk.snapenhance.bridge.DownloadCallback;
import me.rhunk.snapenhance.bridge.SyncCallback; import me.rhunk.snapenhance.bridge.SyncCallback;
interface BridgeInterface { interface BridgeInterface {
/**
* broadcast a log message
*/
void broadcastLog(String tag, String level, String message);
/** /**
* Execute a file operation * Execute a file operation
* @param fileType the corresponding file type (see BridgeFileType)
*/ */
byte[] fileOperation(int action, int fileType, in @nullable byte[] content); byte[] fileOperation(int action, int fileType, in @nullable byte[] content);
/** /**
* Get the content of a logged message from the database * Get the content of a logged message from the database
* * @return message ids that are logged
* @param conversationId the ID of the conversation
* @return the content of the message
*/ */
long[] getLoggedMessageIds(String conversationId, int limit); long[] getLoggedMessageIds(String conversationId, int limit);
/** /**
* Get the content of a logged message from the database * 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); @nullable byte[] getMessageLoggerMessage(String conversationId, long id);
/** /**
* Add a message to the message logger database * 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); void addMessageLoggerMessage(String conversationId, long id, in byte[] message);
/** /**
* Delete a message from the message logger database * Delete a message from the message logger database
*
* @param id the ID of the message logger message
*/ */
void deleteMessageLoggerMessage(String conversationId, long id); void deleteMessageLoggerMessage(String conversationId, long id);
/** /**
* Clear the message logger database * Get the application APK path (assets for the conversation exporter)
*/ */
void clearMessageLogger();
String getApplicationApkPath(); String getApplicationApkPath();
/** /**
* Fetch the locales * Fetch the locales
* *
* @return the locale result * @return the map of locales (key: locale short name, value: locale data as json)
*/ */
Map<String, String> fetchLocales(String userLocale); Map<String, String> fetchLocales(String userLocale);
@ -62,11 +56,14 @@ interface BridgeInterface {
/** /**
* Get rules for a given user or conversation * Get rules for a given user or conversation
* @return list of rules (MessagingRuleType)
*/ */
List<String> getRules(String uuid); List<String> getRules(String uuid);
/** /**
* Update rule for a giver user or conversation * Update rule for a giver user or conversation
*
* @param type rule type (MessagingRuleType)
*/ */
void setRule(String uuid, String type, boolean state); void setRule(String uuid, String type, boolean state);
@ -77,8 +74,8 @@ interface BridgeInterface {
/** /**
* Pass all groups and friends to be able to add them to the database * Pass all groups and friends to be able to add them to the database
* @param groups serialized groups * @param groups list of groups (MessagingGroupInfo as json string)
* @param friends serialized friends * @param friends list of friends (MessagingFriendInfo as json string)
*/ */
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends); oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
} }

View File

@ -20,6 +20,8 @@
"downloads": "Downloads", "downloads": "Downloads",
"features": "Features", "features": "Features",
"home": "Home", "home": "Home",
"home_debug": "Debug",
"home_logs": "Logs",
"social": "Social", "social": "Social",
"plugins": "Plugins" "plugins": "Plugins"
}, },
@ -64,8 +66,8 @@
} }
}, },
"action": { "actions": {
"clean_cache": "Clean Cache", "clean_snapchat_cache": "Clean Snapchat Cache",
"clear_message_logger": "Clear Message Logger", "clear_message_logger": "Clear Message Logger",
"refresh_mappings": "Refresh Mappings", "refresh_mappings": "Refresh Mappings",
"open_map": "Choose location on map", "open_map": "Choose location on map",

View File

@ -1,14 +1,12 @@
package me.rhunk.snapenhance package me.rhunk.snapenhance
object Constants { object Constants {
const val TAG = "SnapEnhance"
const val SNAPCHAT_PACKAGE_NAME = "com.snapchat.android" const val SNAPCHAT_PACKAGE_NAME = "com.snapchat.android"
const val VIEW_INJECTED_CODE = 0x7FFFFF02 const val VIEW_INJECTED_CODE = 0x7FFFFF02
val ARROYO_MEDIA_CONTAINER_PROTO_PATH = intArrayOf(4, 4) val ARROYO_MEDIA_CONTAINER_PROTO_PATH = intArrayOf(4, 4)
val ARROYO_STRING_CHAT_MESSAGE_PROTO = ARROYO_MEDIA_CONTAINER_PROTO_PATH + intArrayOf(2, 1) val ARROYO_STRING_CHAT_MESSAGE_PROTO = ARROYO_MEDIA_CONTAINER_PROTO_PATH + intArrayOf(2, 1)
val ARROYO_URL_KEY_PROTO_PATH = intArrayOf(4, 5, 1, 3)
const val ENCRYPTION_PROTO_INDEX = 19 const val ENCRYPTION_PROTO_INDEX = 19
const val ENCRYPTION_PROTO_INDEX_V2 = 4 const val ENCRYPTION_PROTO_INDEX_V2 = 4

View File

@ -2,47 +2,81 @@ package me.rhunk.snapenhance
import android.util.Log import android.util.Log
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import me.rhunk.snapenhance.core.BuildConfig import me.rhunk.snapenhance.core.bridge.BridgeClient
object Logger { enum class LogLevel(
private const val TAG = "SnapEnhance" val letter: String,
val shortName: String,
val priority: Int = Log.INFO
) {
VERBOSE("V", "verbose", Log.VERBOSE),
DEBUG("D", "debug", Log.DEBUG),
INFO("I", "info", Log.INFO),
WARN("W", "warn", Log.WARN),
ERROR("E", "error", Log.ERROR),
ASSERT("A", "assert", Log.ASSERT);
fun log(message: Any?) { companion object {
Log.i(TAG, message.toString()) fun fromLetter(letter: String): LogLevel? {
return values().find { it.letter == letter }
}
fun fromShortName(shortName: String): LogLevel? {
return values().find { it.shortName == shortName }
}
}
}
class Logger(
private val bridgeClient: BridgeClient
) {
companion object {
private const val TAG = "SnapEnhanceCore"
fun directDebug(message: Any?, tag: String = TAG) {
Log.println(Log.DEBUG, tag, message.toString())
}
fun directError(message: Any?, throwable: Throwable, tag: String = TAG) {
Log.println(Log.ERROR, tag, message.toString())
Log.println(Log.ERROR, tag, throwable.toString())
}
fun xposedLog(message: Any?, tag: String = TAG) {
Log.println(Log.INFO, tag, message.toString())
XposedBridge.log("$tag: $message")
}
fun xposedLog(message: Any?, throwable: Throwable, tag: String = TAG) {
Log.println(Log.INFO, tag, message.toString())
XposedBridge.log("$tag: $message")
XposedBridge.log(throwable)
}
} }
fun debug(message: Any?) { private fun internalLog(tag: String, logLevel: LogLevel, message: Any?) {
if (!BuildConfig.DEBUG) return runCatching {
Log.d(TAG, message.toString()) bridgeClient.broadcastLog(tag, logLevel.shortName, message.toString())
}.onFailure {
Log.println(logLevel.priority, tag, message.toString())
}
} }
fun debug(tag: String, message: Any?) { fun debug(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.DEBUG, message)
if (!BuildConfig.DEBUG) return
Log.d(tag, message.toString()) fun error(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.ERROR, message)
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
internalLog(tag, LogLevel.ERROR, message)
internalLog(tag, LogLevel.ERROR, throwable)
} }
fun error(throwable: Throwable) { fun info(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.INFO, message)
Log.e(TAG, "", throwable)
}
fun error(message: Any?) { fun verbose(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.VERBOSE, message)
Log.e(TAG, message.toString())
}
fun error(message: Any?, throwable: Throwable) { fun warn(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.WARN, message)
Log.e(TAG, message.toString(), throwable)
}
fun xposedLog(message: Any?) { fun assert(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.ASSERT, message)
XposedBridge.log(message.toString())
}
fun xposedLog(message: Any?, throwable: Throwable?) {
XposedBridge.log(message.toString())
XposedBridge.log(throwable)
}
fun xposedLog(throwable: Throwable) {
XposedBridge.log(throwable)
}
} }

View File

@ -11,13 +11,13 @@ import android.widget.Toast
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import me.rhunk.snapenhance.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.bridge.wrapper.MappingsWrapper import me.rhunk.snapenhance.core.bridge.wrapper.MappingsWrapper
import me.rhunk.snapenhance.core.config.ModConfig import me.rhunk.snapenhance.core.config.ModConfig
import me.rhunk.snapenhance.core.database.DatabaseAccess
import me.rhunk.snapenhance.core.eventbus.EventBus import me.rhunk.snapenhance.core.eventbus.EventBus
import me.rhunk.snapenhance.data.MessageSender import me.rhunk.snapenhance.data.MessageSender
import me.rhunk.snapenhance.database.DatabaseAccess
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.manager.impl.ActionManager import me.rhunk.snapenhance.manager.impl.ActionManager
import me.rhunk.snapenhance.manager.impl.FeatureManager import me.rhunk.snapenhance.manager.impl.FeatureManager
@ -44,6 +44,7 @@ class ModContext {
private val modConfig = ModConfig() private val modConfig = ModConfig()
val config by modConfig val config by modConfig
val log by lazy { Logger(this.bridgeClient) }
val event = EventBus(this) val event = EventBus(this)
val eventDispatcher = EventDispatcher(this) val eventDispatcher = EventDispatcher(this)
val native = NativeLib() val native = NativeLib()
@ -81,13 +82,13 @@ class ModContext {
} }
} }
fun shortToast(message: Any) { fun shortToast(message: Any?) {
runOnUiThread { runOnUiThread {
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show() Toast.makeText(androidContext, message.toString(), Toast.LENGTH_SHORT).show()
} }
} }
fun longToast(message: Any) { fun longToast(message: Any?) {
runOnUiThread { runOnUiThread {
Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show() Toast.makeText(androidContext, message.toString(), Toast.LENGTH_LONG).show()
} }
@ -108,7 +109,7 @@ class ModContext {
} }
fun crash(message: String, throwable: Throwable? = null) { fun crash(message: String, throwable: Throwable? = null) {
Logger.xposedLog(message, throwable) Logger.xposedLog(message, throwable ?: Exception())
longToast(message) longToast(message)
delayForceCloseApp(100) delayForceCloseApp(100)
} }
@ -123,6 +124,7 @@ class ModContext {
} }
fun reloadConfig() { fun reloadConfig() {
log.verbose("reloading config")
modConfig.loadFromBridge(bridgeClient) modConfig.loadFromBridge(bridgeClient)
native.loadNativeConfig( native.loadNativeConfig(
NativeConfig( NativeConfig(

View File

@ -6,9 +6,9 @@ import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.bridge.BridgeClient
import me.rhunk.snapenhance.bridge.SyncCallback import me.rhunk.snapenhance.bridge.SyncCallback
import me.rhunk.snapenhance.core.BuildConfig import me.rhunk.snapenhance.core.BuildConfig
import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent import me.rhunk.snapenhance.core.eventbus.events.impl.UnaryCallEvent
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
@ -88,7 +88,7 @@ class SnapEnhance {
return@hook return@hook
} }
Logger.debug("Reloading config") appContext.actionManager.onNewIntent(activity.intent)
appContext.reloadConfig() appContext.reloadConfig()
syncRemote() syncRemote()
} }
@ -114,7 +114,7 @@ class SnapEnhance {
syncRemote() syncRemote()
} }
}.also { time -> }.also { time ->
Logger.debug("init took $time") appContext.log.verbose("init took $time")
} }
} }
@ -126,7 +126,7 @@ class SnapEnhance {
actionManager.init() actionManager.init()
} }
}.also { time -> }.also { time ->
Logger.debug("onActivityCreate took $time") appContext.log.verbose("onActivityCreate took $time")
} }
} }
@ -149,7 +149,6 @@ class SnapEnhance {
val database = appContext.database val database = appContext.database
appContext.executeAsync { appContext.executeAsync {
Logger.debug("request remote sync")
appContext.bridgeClient.sync(object : SyncCallback.Stub() { appContext.bridgeClient.sync(object : SyncCallback.Stub() {
override fun syncFriend(uuid: String): String? { override fun syncFriend(uuid: String): String? {
return database.getFriendInfo(uuid)?.toJson() return database.getFriendInfo(uuid)?.toJson()

View File

@ -3,16 +3,9 @@ package me.rhunk.snapenhance.action
import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.ModContext
import java.io.File import java.io.File
abstract class AbstractAction( abstract class AbstractAction{
val nameKey: String
) {
lateinit var context: ModContext lateinit var context: ModContext
/**
* called on the main thread when the mod initialize
*/
open fun init() {}
/** /**
* called when the action is triggered * called when the action is triggered
*/ */

View File

@ -0,0 +1,17 @@
package me.rhunk.snapenhance.action
import me.rhunk.snapenhance.action.impl.CleanCache
import me.rhunk.snapenhance.action.impl.ExportChatMessages
import me.rhunk.snapenhance.action.impl.OpenMap
import kotlin.reflect.KClass
enum class EnumAction(
val key: String,
val clazz: KClass<out AbstractAction>,
val exitOnFinish: Boolean = false,
val isCritical: Boolean = false,
) {
CLEAN_CACHE("clean_snapchat_cache", CleanCache::class, exitOnFinish = true),
EXPORT_CHAT_MESSAGES("export_chat_messages", ExportChatMessages::class),
OPEN_MAP("open_map", OpenMap::class);
}

View File

@ -1,19 +0,0 @@
package me.rhunk.snapenhance.action.impl
import me.rhunk.snapenhance.action.AbstractAction
import me.rhunk.snapenhance.features.impl.AutoUpdater
class CheckForUpdates : AbstractAction("action.check_for_updates") {
override fun run() {
context.executeAsync {
runCatching {
val latestVersion = context.feature(AutoUpdater::class).checkForUpdates()
if (latestVersion == null) {
context.longToast(context.translation["auto_updater.no_update_available"])
}
}.onFailure {
context.longToast(it.message ?: "Failed to check for updates")
}
}
}
}

View File

@ -3,7 +3,7 @@ package me.rhunk.snapenhance.action.impl
import me.rhunk.snapenhance.action.AbstractAction import me.rhunk.snapenhance.action.AbstractAction
import java.io.File import java.io.File
class CleanCache : AbstractAction("action.clean_cache") { class CleanCache : AbstractAction() {
companion object { companion object {
private val FILES = arrayOf( private val FILES = arrayOf(
"files/mbgl-offline.db", "files/mbgl-offline.db",
@ -22,7 +22,7 @@ class CleanCache : AbstractAction("action.clean_cache") {
} }
override fun run() { override fun run() {
FILES.forEach {fileName -> FILES.forEach { fileName ->
val fileCache = File(context.androidContext.dataDir, fileName) val fileCache = File(context.androidContext.dataDir, fileName)
if (fileName.endsWith("*")) { if (fileName.endsWith("*")) {
val parent = fileCache.parentFile ?: throw IllegalStateException("Parent file is null") val parent = fileCache.parentFile ?: throw IllegalStateException("Parent file is null")

View File

@ -1,10 +0,0 @@
package me.rhunk.snapenhance.action.impl
import me.rhunk.snapenhance.action.AbstractAction
class ClearMessageLogger : AbstractAction("action.clear_message_logger") {
override fun run() {
context.bridgeClient.clearMessageLogger()
context.shortToast("Message logger cleared")
}
}

View File

@ -14,10 +14,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.action.AbstractAction import me.rhunk.snapenhance.action.AbstractAction
import me.rhunk.snapenhance.core.database.objects.FriendFeedEntry
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.data.wrapper.impl.Message import me.rhunk.snapenhance.data.wrapper.impl.Message
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
import me.rhunk.snapenhance.database.objects.FriendFeedEntry
import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.Messaging
import me.rhunk.snapenhance.ui.ViewAppearanceHelper import me.rhunk.snapenhance.ui.ViewAppearanceHelper
import me.rhunk.snapenhance.util.CallbackBuilder import me.rhunk.snapenhance.util.CallbackBuilder
@ -26,7 +26,7 @@ import me.rhunk.snapenhance.util.export.MessageExporter
import java.io.File import java.io.File
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
class ExportChatMessages : AbstractAction("action.export_chat_messages") { class ExportChatMessages : AbstractAction() {
private val callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") } private val callbackClass by lazy { context.mappings.getMappedClass("callbacks", "Callback") }
private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") } private val fetchConversationWithMessagesCallbackClass by lazy { context.mappings.getMappedClass("callbacks", "FetchConversationWithMessagesCallback") }
@ -55,7 +55,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
context.runOnUiThread { context.runOnUiThread {
if (dialogLogs.size > 15) dialogLogs.removeAt(0) if (dialogLogs.size > 15) dialogLogs.removeAt(0)
dialogLogs.add(message) dialogLogs.add(message)
Logger.debug("dialog: $message") context.log.debug("dialog: $message")
currentActionDialog!!.setMessage(dialogLogs.joinToString("\n")) currentActionDialog!!.setMessage(dialogLogs.joinToString("\n"))
} }
} }
@ -198,7 +198,6 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
} }
while (true) { while (true) {
Logger.debug("[$conversationName] fetching $lastMessageId")
val messages = fetchMessagesPaginated(conversationId, lastMessageId) val messages = fetchMessagesPaginated(conversationId, lastMessageId)
if (messages.isEmpty()) break if (messages.isEmpty()) break
foundMessages.addAll(messages) foundMessages.addAll(messages)
@ -224,7 +223,7 @@ class ExportChatMessages : AbstractAction("action.export_chat_messages") {
it.readMessages(foundMessages) it.readMessages(foundMessages)
}.onFailure { }.onFailure {
logDialog(context.translation.format("chat_export.export_failed","conversation" to it.message.toString())) logDialog(context.translation.format("chat_export.export_failed","conversation" to it.message.toString()))
Logger.error(it) context.log.error("Failed to read messages", it)
return return
} }
}.exportTo(exportType!!) }.exportTo(exportType!!)

View File

@ -5,7 +5,7 @@ import android.os.Bundle
import me.rhunk.snapenhance.action.AbstractAction import me.rhunk.snapenhance.action.AbstractAction
import me.rhunk.snapenhance.core.BuildConfig import me.rhunk.snapenhance.core.BuildConfig
class OpenMap: AbstractAction("action.open_map") { class OpenMap: AbstractAction() {
override fun run() { override fun run() {
context.runOnUiThread { context.runOnUiThread {
val mapActivityIntent = Intent() val mapActivityIntent = Intent()

View File

@ -1,11 +0,0 @@
package me.rhunk.snapenhance.action.impl
import me.rhunk.snapenhance.action.AbstractAction
import me.rhunk.snapenhance.bridge.types.BridgeFileType
class RefreshMappings : AbstractAction("action.refresh_mappings") {
override fun run() {
context.bridgeClient.deleteFile(BridgeFileType.MAPPINGS)
context.softRestartApp()
}
}

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.bridge package me.rhunk.snapenhance.core.bridge
import android.content.ComponentName import android.content.ComponentName
@ -10,11 +10,13 @@ import android.os.Handler
import android.os.HandlerThread import android.os.HandlerThread
import android.os.IBinder import android.os.IBinder
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import me.rhunk.snapenhance.Logger.xposedLog
import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.bridge.types.BridgeFileType import me.rhunk.snapenhance.bridge.BridgeInterface
import me.rhunk.snapenhance.bridge.types.FileActionType import me.rhunk.snapenhance.bridge.DownloadCallback
import me.rhunk.snapenhance.bridge.SyncCallback
import me.rhunk.snapenhance.core.BuildConfig 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.MessagingRuleType
import me.rhunk.snapenhance.data.LocalePair import me.rhunk.snapenhance.data.LocalePair
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@ -29,7 +31,7 @@ class BridgeClient(
private lateinit var service: BridgeInterface private lateinit var service: BridgeInterface
companion object { companion object {
const val BRIDGE_SYNC_ACTION = "me.rhunk.snapenhance.bridge.SYNC" const val BRIDGE_SYNC_ACTION = "me.rhunk.snapenhance.core.bridge.SYNC"
} }
fun start(callback: (Boolean) -> Unit) { fun start(callback: (Boolean) -> Unit) {
@ -76,7 +78,7 @@ class BridgeClient(
} }
override fun onNullBinding(name: ComponentName) { override fun onNullBinding(name: ComponentName) {
xposedLog("failed to connect to bridge service") context.log.error("BridgeClient", "failed to connect to bridge service")
exitProcess(1) exitProcess(1)
} }
@ -84,6 +86,8 @@ class BridgeClient(
exitProcess(0) exitProcess(0)
} }
fun broadcastLog(tag: String, level: String, message: String) = service.broadcastLog(tag, level, message)
fun createAndReadFile( fun createAndReadFile(
fileType: BridgeFileType, fileType: BridgeFileType,
defaultContent: ByteArray defaultContent: ByteArray
@ -108,8 +112,6 @@ class BridgeClient(
fun deleteMessageLoggerMessage(conversationId: String, id: Long) = service.deleteMessageLoggerMessage(conversationId, id) fun deleteMessageLoggerMessage(conversationId: String, id: Long) = service.deleteMessageLoggerMessage(conversationId, id)
fun clearMessageLogger() = service.clearMessageLogger()
fun fetchLocales(userLocale: String) = service.fetchLocales(userLocale).map { fun fetchLocales(userLocale: String) = service.fetchLocales(userLocale).map {
LocalePair(it.key, it.value) LocalePair(it.key, it.value)
} }

View File

@ -1,7 +1,7 @@
package me.rhunk.snapenhance.bridge package me.rhunk.snapenhance.core.bridge
import android.content.Context import android.content.Context
import me.rhunk.snapenhance.bridge.types.BridgeFileType import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
open class FileLoaderWrapper( open class FileLoaderWrapper(
private val fileType: BridgeFileType, private val fileType: BridgeFileType,

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.bridge.types package me.rhunk.snapenhance.core.bridge.types
import android.content.Context import android.content.Context
import java.io.File import java.io.File

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.bridge.types package me.rhunk.snapenhance.core.bridge.types
enum class FileActionType { enum class FileActionType {
CREATE_AND_READ, READ, WRITE, DELETE, EXISTS CREATE_AND_READ, READ, WRITE, DELETE, EXISTS

View File

@ -1,10 +1,10 @@
package me.rhunk.snapenhance.bridge.wrapper package me.rhunk.snapenhance.core.bridge.wrapper
import android.content.Context import android.content.Context
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.data.LocalePair import me.rhunk.snapenhance.data.LocalePair
import java.util.Locale import java.util.Locale
@ -80,7 +80,7 @@ class LocaleWrapper {
loadFromContext(context) loadFromContext(context)
} }
operator fun get(key: String) = translationMap[key] ?: key.also { Logger.debug("Missing translation for $key") } operator fun get(key: String) = translationMap[key] ?: key.also { Logger.directDebug("Missing translation for $key") }
fun format(key: String, vararg args: Pair<String, String>): String { fun format(key: String, vararg args: Pair<String, String>): String {
return args.fold(get(key)) { acc, pair -> return args.fold(get(key)) { acc, pair ->

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.bridge.wrapper package me.rhunk.snapenhance.core.bridge.wrapper
import android.content.Context import android.content.Context
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
@ -6,8 +6,8 @@ import com.google.gson.JsonElement
import com.google.gson.JsonParser import com.google.gson.JsonParser
import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.bridge.FileLoaderWrapper import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper
import me.rhunk.snapenhance.bridge.types.BridgeFileType import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import me.rhunk.snapmapper.Mapper import me.rhunk.snapmapper.Mapper
import me.rhunk.snapmapper.impl.BCryptClassMapper import me.rhunk.snapmapper.impl.BCryptClassMapper
import me.rhunk.snapmapper.impl.CallbackMapper import me.rhunk.snapmapper.impl.CallbackMapper
@ -58,11 +58,8 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
runCatching { runCatching {
loadCached() loadCached()
}.onFailure { }.onFailure {
Logger.error("Failed to load cached mappings", it)
delete() delete()
} }
} else {
Logger.debug("Mappings file does not exist")
} }
} }
@ -122,7 +119,7 @@ class MappingsWrapper : FileLoaderWrapper(BridgeFileType.MAPPINGS, "{}".toByteAr
} }
write(result.toString().toByteArray()) write(result.toString().toByteArray())
}.also { }.also {
Logger.debug("Generated mappings in $it ms") Logger.directDebug("Generated mappings in $it ms")
} }
} }

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.bridge.wrapper package me.rhunk.snapenhance.core.bridge.wrapper
import android.content.ContentValues import android.content.ContentValues
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase

View File

@ -1,6 +1,6 @@
package me.rhunk.snapenhance.core.config package me.rhunk.snapenhance.core.config
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
import kotlin.reflect.KProperty import kotlin.reflect.KProperty

View File

@ -4,11 +4,10 @@ import android.content.Context
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.JsonObject import com.google.gson.JsonObject
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.core.bridge.BridgeClient
import me.rhunk.snapenhance.bridge.BridgeClient import me.rhunk.snapenhance.core.bridge.FileLoaderWrapper
import me.rhunk.snapenhance.bridge.FileLoaderWrapper import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import me.rhunk.snapenhance.bridge.types.BridgeFileType import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.bridge.wrapper.LocaleWrapper
import me.rhunk.snapenhance.core.config.impl.RootConfig import me.rhunk.snapenhance.core.config.impl.RootConfig
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -33,7 +32,6 @@ class ModConfig {
runCatching { runCatching {
loadConfig() loadConfig()
}.onFailure { }.onFailure {
Logger.error("Failed to load config", it)
writeConfig() writeConfig()
} }
} }

View File

@ -1,6 +1,7 @@
package me.rhunk.snapenhance.core.config.impl package me.rhunk.snapenhance.core.config.impl
import me.rhunk.snapenhance.core.config.ConfigContainer import me.rhunk.snapenhance.core.config.ConfigContainer
import me.rhunk.snapenhance.core.config.FeatureNotice
class Experimental : ConfigContainer() { class Experimental : ConfigContainer() {
val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory" } val nativeHooks = container("native_hooks", NativeHooks()) { icon = "Memory" }
@ -9,6 +10,6 @@ class Experimental : ConfigContainer() {
val appLockOnResume = boolean("app_lock_on_resume") val appLockOnResume = boolean("app_lock_on_resume")
val infiniteStoryBoost = boolean("infinite_story_boost") val infiniteStoryBoost = boolean("infinite_story_boost")
val meoPasscodeBypass = boolean("meo_passcode_bypass") val meoPasscodeBypass = boolean("meo_passcode_bypass")
val unlimitedMultiSnap = boolean("unlimited_multi_snap") val unlimitedMultiSnap = boolean("unlimited_multi_snap") { addNotices(FeatureNotice.MAY_BAN)}
val noFriendScoreDelay = boolean("no_friend_score_delay") val noFriendScoreDelay = boolean("no_friend_score_delay")
} }

View File

@ -1,10 +1,14 @@
package me.rhunk.snapenhance.database package me.rhunk.snapenhance.core.database
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.database.objects.* import me.rhunk.snapenhance.core.database.objects.ConversationMessage
import me.rhunk.snapenhance.core.database.objects.FriendFeedEntry
import me.rhunk.snapenhance.core.database.objects.FriendInfo
import me.rhunk.snapenhance.core.database.objects.StoryEntry
import me.rhunk.snapenhance.core.database.objects.UserConversationLink
import me.rhunk.snapenhance.manager.Manager import me.rhunk.snapenhance.manager.Manager
import java.io.File import java.io.File
@ -68,7 +72,7 @@ class DatabaseAccess(private val context: ModContext) : Manager {
try { try {
obj.write(cursor) obj.write(cursor)
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.xposedLog(e) context.log.error("Failed to read database object", e)
} }
cursor.close() cursor.close()
return obj return obj

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.database package me.rhunk.snapenhance.core.database
import android.database.Cursor import android.database.Cursor

View File

@ -1,10 +1,10 @@
package me.rhunk.snapenhance.database.objects package me.rhunk.snapenhance.core.database.objects
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.Cursor import android.database.Cursor
import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.core.database.DatabaseObject
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.database.DatabaseObject
import me.rhunk.snapenhance.util.ktx.getBlobOrNull import me.rhunk.snapenhance.util.ktx.getBlobOrNull
import me.rhunk.snapenhance.util.ktx.getInteger import me.rhunk.snapenhance.util.ktx.getInteger
import me.rhunk.snapenhance.util.ktx.getLong import me.rhunk.snapenhance.util.ktx.getLong

View File

@ -1,8 +1,8 @@
package me.rhunk.snapenhance.database.objects package me.rhunk.snapenhance.core.database.objects
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.Cursor import android.database.Cursor
import me.rhunk.snapenhance.database.DatabaseObject import me.rhunk.snapenhance.core.database.DatabaseObject
import me.rhunk.snapenhance.util.ktx.getIntOrNull import me.rhunk.snapenhance.util.ktx.getIntOrNull
import me.rhunk.snapenhance.util.ktx.getInteger import me.rhunk.snapenhance.util.ktx.getInteger
import me.rhunk.snapenhance.util.ktx.getLong import me.rhunk.snapenhance.util.ktx.getLong

View File

@ -1,8 +1,8 @@
package me.rhunk.snapenhance.database.objects package me.rhunk.snapenhance.core.database.objects
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.Cursor import android.database.Cursor
import me.rhunk.snapenhance.database.DatabaseObject import me.rhunk.snapenhance.core.database.DatabaseObject
import me.rhunk.snapenhance.util.SerializableDataObject import me.rhunk.snapenhance.util.SerializableDataObject
import me.rhunk.snapenhance.util.ktx.getInteger import me.rhunk.snapenhance.util.ktx.getInteger
import me.rhunk.snapenhance.util.ktx.getLong import me.rhunk.snapenhance.util.ktx.getLong

View File

@ -1,8 +1,8 @@
package me.rhunk.snapenhance.database.objects package me.rhunk.snapenhance.core.database.objects
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.Cursor import android.database.Cursor
import me.rhunk.snapenhance.database.DatabaseObject import me.rhunk.snapenhance.core.database.DatabaseObject
import me.rhunk.snapenhance.util.ktx.getInteger import me.rhunk.snapenhance.util.ktx.getInteger
import me.rhunk.snapenhance.util.ktx.getStringOrNull import me.rhunk.snapenhance.util.ktx.getStringOrNull

View File

@ -1,8 +1,8 @@
package me.rhunk.snapenhance.database.objects package me.rhunk.snapenhance.core.database.objects
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.database.Cursor import android.database.Cursor
import me.rhunk.snapenhance.database.DatabaseObject import me.rhunk.snapenhance.core.database.DatabaseObject
import me.rhunk.snapenhance.util.ktx.getInteger import me.rhunk.snapenhance.util.ktx.getInteger
import me.rhunk.snapenhance.util.ktx.getStringOrNull import me.rhunk.snapenhance.util.ktx.getStringOrNull

View File

@ -1,15 +1,15 @@
package me.rhunk.snapenhance.download package me.rhunk.snapenhance.core.download
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.bridge.DownloadCallback import me.rhunk.snapenhance.bridge.DownloadCallback
import me.rhunk.snapenhance.download.data.DashOptions import me.rhunk.snapenhance.core.download.data.DashOptions
import me.rhunk.snapenhance.download.data.DownloadMediaType import me.rhunk.snapenhance.core.download.data.DownloadMediaType
import me.rhunk.snapenhance.download.data.DownloadMetadata import me.rhunk.snapenhance.core.download.data.DownloadMetadata
import me.rhunk.snapenhance.download.data.DownloadRequest import me.rhunk.snapenhance.core.download.data.DownloadRequest
import me.rhunk.snapenhance.download.data.InputMedia import me.rhunk.snapenhance.core.download.data.InputMedia
import me.rhunk.snapenhance.download.data.MediaEncryptionKeyPair import me.rhunk.snapenhance.core.download.data.MediaEncryptionKeyPair
class DownloadManagerClient ( class DownloadManagerClient (
private val context: ModContext, private val context: ModContext,
@ -33,10 +33,12 @@ class DownloadManagerClient (
fun downloadDashMedia(playlistUrl: String, offsetTime: Long, duration: Long?) { fun downloadDashMedia(playlistUrl: String, offsetTime: Long, duration: Long?) {
enqueueDownloadRequest( enqueueDownloadRequest(
DownloadRequest( DownloadRequest(
inputMedias = arrayOf(InputMedia( inputMedias = arrayOf(
InputMedia(
content = playlistUrl, content = playlistUrl,
type = DownloadMediaType.REMOTE_MEDIA type = DownloadMediaType.REMOTE_MEDIA
)), )
),
dashOptions = DashOptions(offsetTime, duration), dashOptions = DashOptions(offsetTime, duration),
flags = DownloadRequest.Flags.IS_DASH_PLAYLIST flags = DownloadRequest.Flags.IS_DASH_PLAYLIST
) )
@ -46,11 +48,13 @@ class DownloadManagerClient (
fun downloadSingleMedia(mediaData: String, mediaType: DownloadMediaType, encryption: MediaEncryptionKeyPair? = null) { fun downloadSingleMedia(mediaData: String, mediaType: DownloadMediaType, encryption: MediaEncryptionKeyPair? = null) {
enqueueDownloadRequest( enqueueDownloadRequest(
DownloadRequest( DownloadRequest(
inputMedias = arrayOf(InputMedia( inputMedias = arrayOf(
InputMedia(
content = mediaData, content = mediaData,
type = mediaType, type = mediaType,
encryption = encryption encryption = encryption
)) )
)
) )
) )
} }

View File

@ -1,12 +1,12 @@
package me.rhunk.snapenhance.download package me.rhunk.snapenhance.core.download
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import me.rhunk.snapenhance.download.data.DownloadMetadata import me.rhunk.snapenhance.core.download.data.DownloadMetadata
import me.rhunk.snapenhance.download.data.DownloadObject import me.rhunk.snapenhance.core.download.data.DownloadObject
import me.rhunk.snapenhance.download.data.DownloadStage import me.rhunk.snapenhance.core.download.data.DownloadStage
import me.rhunk.snapenhance.download.data.MediaFilter import me.rhunk.snapenhance.core.download.data.MediaFilter
import me.rhunk.snapenhance.util.SQLiteDatabaseHelper import me.rhunk.snapenhance.util.SQLiteDatabaseHelper
import me.rhunk.snapenhance.util.ktx.getIntOrNull import me.rhunk.snapenhance.util.ktx.getIntOrNull
import me.rhunk.snapenhance.util.ktx.getStringOrNull import me.rhunk.snapenhance.util.ktx.getStringOrNull

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
import android.net.Uri import android.net.Uri

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
data class DownloadMetadata( data class DownloadMetadata(
val mediaIdentifier: String?, val mediaIdentifier: String?,

View File

@ -1,7 +1,7 @@
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import me.rhunk.snapenhance.download.DownloadTaskManager import me.rhunk.snapenhance.core.download.DownloadTaskManager
data class DownloadObject( data class DownloadObject(
var downloadId: Int = 0, var downloadId: Int = 0,

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
data class DashOptions(val offsetTime: Long, val duration: Long?) data class DashOptions(val offsetTime: Long, val duration: Long?)

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
enum class DownloadStage( enum class DownloadStage(
val isFinalStage: Boolean = false, val isFinalStage: Boolean = false,

View File

@ -1,6 +1,6 @@
@file:OptIn(ExperimentalEncodingApi::class) @file:OptIn(ExperimentalEncodingApi::class)
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
import me.rhunk.snapenhance.data.wrapper.impl.media.EncryptionWrapper import me.rhunk.snapenhance.data.wrapper.impl.media.EncryptionWrapper
import kotlin.io.encoding.Base64 import kotlin.io.encoding.Base64

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
enum class MediaFilter( enum class MediaFilter(
val key: String, val key: String,

View File

@ -1,4 +1,4 @@
package me.rhunk.snapenhance.download.data package me.rhunk.snapenhance.core.download.data
enum class SplitMediaAssetType { enum class SplitMediaAssetType {
ORIGINAL, OVERLAY ORIGINAL, OVERLAY

View File

@ -1,6 +1,5 @@
package me.rhunk.snapenhance.core.eventbus package me.rhunk.snapenhance.core.eventbus
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.ModContext
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -14,7 +13,7 @@ interface IListener<T> {
} }
class EventBus( class EventBus(
private val context: ModContext val context: ModContext
) { ) {
private val subscribers = mutableMapOf<KClass<out Event>, MutableList<IListener<out Event>>>() private val subscribers = mutableMapOf<KClass<out Event>, MutableList<IListener<out Event>>>()
@ -34,7 +33,7 @@ class EventBus(
runCatching { runCatching {
listener(event) listener(event)
}.onFailure { }.onFailure {
Logger.error("Error while handling event ${event::class.simpleName}", it) context.log.error("Error while handling event ${event::class.simpleName}", it)
} }
} }
} }
@ -61,7 +60,7 @@ class EventBus(
runCatching { runCatching {
(listener as IListener<T>).handle(event) (listener as IListener<T>).handle(event)
}.onFailure { t -> }.onFailure { t ->
Logger.error("Error while handling event ${event::class.simpleName} by ${listener::class.simpleName}", t) context.log.error("Error while handling event ${event::class.simpleName} by ${listener::class.simpleName}", t)
} }
} }
return event return event

View File

@ -1,6 +1,6 @@
package me.rhunk.snapenhance.features package me.rhunk.snapenhance.features
import me.rhunk.snapenhance.bridge.types.BridgeFileType import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import java.io.BufferedReader import java.io.BufferedReader
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStreamReader import java.io.InputStreamReader

View File

@ -10,7 +10,6 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment import android.os.Environment
import com.google.gson.JsonParser import com.google.gson.JsonParser
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.core.BuildConfig import me.rhunk.snapenhance.core.BuildConfig
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams
@ -36,7 +35,7 @@ class AutoUpdater : Feature("AutoUpdater", loadParams = FeatureLoadParams.ACTIVI
runCatching { runCatching {
checkForUpdates() checkForUpdates()
}.onFailure { }.onFailure {
Logger.error("Failed to check for updates: ${it.message}", it) context.log.error("Failed to check for updates: ${it.message}", it)
}.onSuccess { }.onSuccess {
context.bridgeClient.setAutoUpdaterTime(currentTimeMillis) context.bridgeClient.setAutoUpdaterTime(currentTimeMillis)
} }

View File

@ -6,9 +6,15 @@ import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.widget.ImageView import android.widget.ImageView
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.Logger.xposedLog
import me.rhunk.snapenhance.bridge.DownloadCallback import me.rhunk.snapenhance.bridge.DownloadCallback
import me.rhunk.snapenhance.core.database.objects.FriendInfo
import me.rhunk.snapenhance.core.download.DownloadManagerClient
import me.rhunk.snapenhance.core.download.data.DownloadMediaType
import me.rhunk.snapenhance.core.download.data.DownloadMetadata
import me.rhunk.snapenhance.core.download.data.InputMedia
import me.rhunk.snapenhance.core.download.data.MediaFilter
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
import me.rhunk.snapenhance.core.download.data.toKeyPair
import me.rhunk.snapenhance.core.messaging.MessagingRuleType import me.rhunk.snapenhance.core.messaging.MessagingRuleType
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.data.FileType import me.rhunk.snapenhance.data.FileType
@ -17,14 +23,6 @@ import me.rhunk.snapenhance.data.wrapper.impl.media.dash.LongformVideoPlaylistIt
import me.rhunk.snapenhance.data.wrapper.impl.media.dash.SnapPlaylistItem import me.rhunk.snapenhance.data.wrapper.impl.media.dash.SnapPlaylistItem
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.Layer import me.rhunk.snapenhance.data.wrapper.impl.media.opera.Layer
import me.rhunk.snapenhance.data.wrapper.impl.media.opera.ParamMap import me.rhunk.snapenhance.data.wrapper.impl.media.opera.ParamMap
import me.rhunk.snapenhance.database.objects.FriendInfo
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.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.features.MessagingRuleFeature import me.rhunk.snapenhance.features.MessagingRuleFeature
import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.Messaging
@ -84,19 +82,19 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
callback = object: DownloadCallback.Stub() { callback = object: DownloadCallback.Stub() {
override fun onSuccess(outputFile: String) { override fun onSuccess(outputFile: String) {
if (!downloadLogging.contains("success")) return if (!downloadLogging.contains("success")) return
Logger.debug("onSuccess: outputFile=$outputFile") context.log.verbose("onSuccess: outputFile=$outputFile")
context.shortToast(context.translation.format("download_processor.saved_toast", "path" to outputFile.split("/").takeLast(2).joinToString("/"))) context.shortToast(context.translation.format("download_processor.saved_toast", "path" to outputFile.split("/").takeLast(2).joinToString("/")))
} }
override fun onProgress(message: String) { override fun onProgress(message: String) {
if (!downloadLogging.contains("progress")) return if (!downloadLogging.contains("progress")) return
Logger.debug("onProgress: message=$message") context.log.verbose("onProgress: message=$message")
context.shortToast(message) context.shortToast(message)
} }
override fun onFailure(message: String, throwable: String?) { override fun onFailure(message: String, throwable: String?) {
if (!downloadLogging.contains("failure")) return if (!downloadLogging.contains("failure")) return
Logger.debug("onFailure: message=$message, throwable=$throwable") context.log.verbose("onFailure: message=$message, throwable=$throwable")
throwable?.let { throwable?.let {
context.longToast((message + it.takeIf { it.isNotEmpty() }.orEmpty())) context.longToast((message + it.takeIf { it.isNotEmpty() }.orEmpty()))
return return
@ -402,8 +400,8 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
try { try {
handleOperaMedia(mediaParamMap, mediaInfoMap, false) handleOperaMedia(mediaParamMap, mediaInfoMap, false)
} catch (e: Throwable) { } catch (e: Throwable) {
xposedLog(e) context.log.error("Failed to handle opera media", e)
context.longToast(e.message!!) context.longToast(e.message)
} }
} }
} }
@ -524,11 +522,11 @@ class MediaDownloader : MessagingRuleFeature("MediaDownloader", MessagingRuleTyp
} }
}.onFailure { }.onFailure {
context.shortToast(translations["failed_to_create_preview_toast"]) context.shortToast(translations["failed_to_create_preview_toast"])
xposedLog(it) context.log.error("Failed to create preview", it)
} }
}.onFailure { }.onFailure {
context.longToast(translations["failed_generic_toast"]) context.longToast(translations["failed_generic_toast"])
xposedLog(it) context.log.error("Failed to download message", it)
} }
} }

View File

@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.downloader
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.widget.Button import android.widget.Button
import android.widget.RelativeLayout import android.widget.RelativeLayout
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent import me.rhunk.snapenhance.core.eventbus.events.impl.AddViewEvent
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
@ -45,7 +44,7 @@ class ProfilePictureDownloader : Feature("ProfilePictureDownloader", loadParams
friendUsername!! friendUsername!!
) )
}.onFailure { }.onFailure {
Logger.error("Failed to download profile picture", it) this@ProfilePictureDownloader.context.log.error("Failed to download profile picture", it)
} }
} }
}.show() }.show()

View File

@ -1,6 +1,5 @@
package me.rhunk.snapenhance.features.impl.experiments package me.rhunk.snapenhance.features.impl.experiments
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.HookStage
@ -17,11 +16,11 @@ class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParam
val fingerprintClass = android.os.Build::class.java val fingerprintClass = android.os.Build::class.java
Hooker.hook(fingerprintClass, "FINGERPRINT", HookStage.BEFORE) { hookAdapter -> Hooker.hook(fingerprintClass, "FINGERPRINT", HookStage.BEFORE) { hookAdapter ->
hookAdapter.setResult(fingerprint) hookAdapter.setResult(fingerprint)
Logger.debug("Fingerprint spoofed to $fingerprint") context.log.verbose("Fingerprint spoofed to $fingerprint")
} }
Hooker.hook(fingerprintClass, "deriveFingerprint", HookStage.BEFORE) { hookAdapter -> Hooker.hook(fingerprintClass, "deriveFingerprint", HookStage.BEFORE) { hookAdapter ->
hookAdapter.setResult(fingerprint) hookAdapter.setResult(fingerprint)
Logger.debug("Fingerprint spoofed to $fingerprint") context.log.verbose("Fingerprint spoofed to $fingerprint")
} }
} }
@ -30,7 +29,7 @@ class DeviceSpooferHook: Feature("device_spoofer", loadParams = FeatureLoadParam
Hooker.hook(settingsSecureClass, "getString", HookStage.BEFORE) { hookAdapter -> Hooker.hook(settingsSecureClass, "getString", HookStage.BEFORE) { hookAdapter ->
if(hookAdapter.args()[1] == "android_id") { if(hookAdapter.args()[1] == "android_id") {
hookAdapter.setResult(androidId) hookAdapter.setResult(androidId)
Logger.debug("Android ID spoofed to $androidId") context.log.verbose("Android ID spoofed to $androidId")
} }
} }
} }

View File

@ -1,6 +1,5 @@
package me.rhunk.snapenhance.features.impl.privacy package me.rhunk.snapenhance.features.impl.privacy
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
import me.rhunk.snapenhance.data.NotificationType import me.rhunk.snapenhance.data.NotificationType
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
@ -28,7 +27,7 @@ class PreventMessageSending : Feature("Prevent message sending", loadParams = Fe
val associatedType = NotificationType.fromContentType(contentType) ?: return@subscribe val associatedType = NotificationType.fromContentType(contentType) ?: return@subscribe
if (preventMessageSending.contains(associatedType.key)) { if (preventMessageSending.contains(associatedType.key)) {
Logger.debug("Preventing message sending for $associatedType") context.log.verbose("Preventing message sending for $associatedType")
event.canceled = true event.canceled = true
} }
} }

View File

@ -1,7 +1,6 @@
package me.rhunk.snapenhance.features.impl.spying package me.rhunk.snapenhance.features.impl.spying
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent import me.rhunk.snapenhance.core.eventbus.events.impl.NetworkApiRequestEvent
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams

View File

@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.spying
import android.os.DeadObjectException import android.os.DeadObjectException
import com.google.gson.JsonObject import com.google.gson.JsonObject
import com.google.gson.JsonParser import com.google.gson.JsonParser
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.data.MessageState import me.rhunk.snapenhance.data.MessageState
import me.rhunk.snapenhance.data.wrapper.impl.Message import me.rhunk.snapenhance.data.wrapper.impl.Message
@ -72,7 +71,7 @@ class MessageLogger : Feature("MessageLogger",
context.database.getFeedEntries(PREFETCH_FEED_COUNT).forEach { friendFeedInfo -> context.database.getFeedEntries(PREFETCH_FEED_COUNT).forEach { friendFeedInfo ->
fetchedMessages.addAll(context.bridgeClient.getLoggedMessageIds(friendFeedInfo.key!!, PREFETCH_MESSAGE_COUNT).toList()) fetchedMessages.addAll(context.bridgeClient.getLoggedMessageIds(friendFeedInfo.key!!, PREFETCH_MESSAGE_COUNT).toList())
} }
}.also { Logger.debug("Loaded ${fetchedMessages.size} cached messages in $it") } }.also { context.log.verbose("Loaded ${fetchedMessages.size} cached messages in $it") }
} }
private fun processSnapMessage(messageInstance: Any) { private fun processSnapMessage(messageInstance: Any) {

View File

@ -41,7 +41,7 @@ class AutoSave : MessagingRuleFeature("Auto Save", MessagingRuleType.AUTO_SAVE,
val callback = CallbackBuilder(callbackClass) val callback = CallbackBuilder(callbackClass)
.override("onError") { .override("onError") {
Logger.xposedLog("Error saving message $messageId") context.log.warn("Error saving message $messageId")
}.build() }.build()
runCatching { runCatching {

View File

@ -3,7 +3,6 @@ package me.rhunk.snapenhance.features.impl.tweaks
import android.os.Build import android.os.Build
import android.os.FileObserver import android.os.FileObserver
import com.google.gson.JsonParser import com.google.gson.JsonParser
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent import me.rhunk.snapenhance.core.eventbus.events.impl.SendMessageWithContentEvent
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams
@ -32,7 +31,7 @@ class DisableVideoLengthRestriction : Feature("DisableVideoLengthRestriction", l
val fileContent = JsonParser.parseReader(file.reader()).asJsonObject val fileContent = JsonParser.parseReader(file.reader()).asJsonObject
if (fileContent["timerOrDuration"].asLong < 0) file.delete() if (fileContent["timerOrDuration"].asLong < 0) file.delete()
}.onFailure { }.onFailure {
Logger.error("Failed to read story metadata file", it) context.log.error("Failed to read story metadata file", it)
} }
} }
}) })

View File

@ -1,7 +1,6 @@
package me.rhunk.snapenhance.features.impl.tweaks package me.rhunk.snapenhance.features.impl.tweaks
import android.app.AlertDialog import android.app.AlertDialog
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.hook.HookStage import me.rhunk.snapenhance.hook.HookStage
@ -15,7 +14,7 @@ class GooglePlayServicesDialogs : Feature("Disable GMS Dialogs", loadParams = Fe
findClass("com.google.android.gms.common.GoogleApiAvailability").methods findClass("com.google.android.gms.common.GoogleApiAvailability").methods
.first { Modifier.isStatic(it.modifiers) && it.returnType == AlertDialog::class.java }.let { method -> .first { Modifier.isStatic(it.modifiers) && it.returnType == AlertDialog::class.java }.let { method ->
method.hook(HookStage.BEFORE) { param -> method.hook(HookStage.BEFORE) { param ->
Logger.debug("GoogleApiAvailability.showErrorDialogFragment() called, returning null") context.log.verbose("GoogleApiAvailability.showErrorDialogFragment() called, returning null")
param.setResult(null) param.setResult(null)
} }
} }

View File

@ -13,12 +13,12 @@ import android.os.UserHandle
import de.robv.android.xposed.XposedBridge import de.robv.android.xposed.XposedBridge
import de.robv.android.xposed.XposedHelpers import de.robv.android.xposed.XposedHelpers
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent import me.rhunk.snapenhance.core.eventbus.events.impl.SnapWidgetBroadcastReceiveEvent
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.data.MediaReferenceType import me.rhunk.snapenhance.data.MediaReferenceType
import me.rhunk.snapenhance.data.wrapper.impl.Message import me.rhunk.snapenhance.data.wrapper.impl.Message
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
import me.rhunk.snapenhance.features.Feature import me.rhunk.snapenhance.features.Feature
import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams
import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.Messaging
@ -297,7 +297,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
fetchMessagesResult(conversationId, messageList) fetchMessagesResult(conversationId, messageList)
} }
.override("onError") { .override("onError") {
Logger.xposedLog("Failed to fetch message ${it.arg(0) as Any}") context.log.error("Failed to fetch message ${it.arg(0) as Any}")
}.build() }.build()
fetchConversationWithMessagesMethod.invoke(conversationManager, SnapUUID.fromString(conversationId).instanceNonNull(), callback) fetchConversationWithMessagesMethod.invoke(conversationManager, SnapUUID.fromString(conversationId).instanceNonNull(), callback)
@ -323,7 +323,7 @@ class Notifications : Feature("Notifications", loadParams = FeatureLoadParams.IN
val intent = param.argNullable<Intent>(0) ?: return@hook val intent = param.argNullable<Intent>(0) ?: return@hook
val messageType = intent.getStringExtra("type") ?: return@hook val messageType = intent.getStringExtra("type") ?: return@hook
Logger.xposedLog("received message type: $messageType") context.log.debug("received message type: $messageType")
if (states.contains(messageType.replaceFirst("mischief_", ""))) { if (states.contains(messageType.replaceFirst("mischief_", ""))) {
param.setResult(null) param.setResult(null)

View File

@ -1,6 +1,6 @@
package me.rhunk.snapenhance.features.impl.ui package me.rhunk.snapenhance.features.impl.ui
import me.rhunk.snapenhance.bridge.types.BridgeFileType import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
import me.rhunk.snapenhance.features.BridgeFileFeature import me.rhunk.snapenhance.features.BridgeFileFeature
import me.rhunk.snapenhance.features.FeatureLoadParams import me.rhunk.snapenhance.features.FeatureLoadParams

View File

@ -1,38 +1,37 @@
package me.rhunk.snapenhance.manager.impl package me.rhunk.snapenhance.manager.impl
import android.content.Intent
import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.action.AbstractAction import me.rhunk.snapenhance.action.AbstractAction
import me.rhunk.snapenhance.action.impl.CheckForUpdates import me.rhunk.snapenhance.action.EnumAction
import me.rhunk.snapenhance.action.impl.CleanCache
import me.rhunk.snapenhance.action.impl.ClearMessageLogger
import me.rhunk.snapenhance.action.impl.ExportChatMessages
import me.rhunk.snapenhance.action.impl.OpenMap
import me.rhunk.snapenhance.action.impl.RefreshMappings
import me.rhunk.snapenhance.core.BuildConfig
import me.rhunk.snapenhance.manager.Manager import me.rhunk.snapenhance.manager.Manager
import kotlin.reflect.KClass
class ActionManager( class ActionManager(
private val context: ModContext, private val modContext: ModContext,
) : Manager { ) : Manager {
private val actions = mutableMapOf<String, AbstractAction>() companion object {
fun getActions() = actions.values.toList() const val ACTION_PARAMETER = "se_action"
private fun load(clazz: KClass<out AbstractAction>) {
val action = clazz.java.newInstance()
action.context = context
actions[action.nameKey] = action
} }
private val actions = mutableMapOf<String, AbstractAction>()
override fun init() { override fun init() {
load(CleanCache::class) EnumAction.values().forEach { enumAction ->
load(ExportChatMessages::class) actions[enumAction.key] = enumAction.clazz.java.getConstructor().newInstance().apply {
load(OpenMap::class) this.context = modContext
load(CheckForUpdates::class) }
if(BuildConfig.DEBUG) {
load(ClearMessageLogger::class)
load(RefreshMappings::class)
} }
}
fun onNewIntent(intent: Intent?) {
val action = intent?.getStringExtra(ACTION_PARAMETER) ?: return
execute(EnumAction.values().find { it.key == action } ?: return)
intent.removeExtra(ACTION_PARAMETER)
}
actions.values.forEach(AbstractAction::init) private fun execute(action: EnumAction) {
actions[action.key]?.run()
if (action.exitOnFinish) {
modContext.forceCloseApp()
}
} }
} }

View File

@ -11,11 +11,10 @@ import android.view.View
import android.widget.Button import android.widget.Button
import android.widget.CompoundButton import android.widget.CompoundButton
import android.widget.Switch import android.widget.Switch
import me.rhunk.snapenhance.Logger import me.rhunk.snapenhance.core.database.objects.ConversationMessage
import me.rhunk.snapenhance.core.database.objects.FriendInfo
import me.rhunk.snapenhance.core.database.objects.UserConversationLink
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.database.objects.ConversationMessage
import me.rhunk.snapenhance.database.objects.FriendInfo
import me.rhunk.snapenhance.database.objects.UserConversationLink
import me.rhunk.snapenhance.features.MessagingRuleFeature import me.rhunk.snapenhance.features.MessagingRuleFeature
import me.rhunk.snapenhance.features.impl.Messaging import me.rhunk.snapenhance.features.impl.Messaging
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
@ -57,7 +56,7 @@ class FriendFeedInfoMenu : AbstractMenu() {
) )
} }
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.xposedLog(e) context.log.error("Error loading bitmoji selfie", e)
} }
val finalIcon = icon val finalIcon = icon
context.runOnUiThread { context.runOnUiThread {
@ -243,7 +242,6 @@ class FriendFeedInfoMenu : AbstractMenu() {
rules.forEach { ruleFeature -> rules.forEach { ruleFeature ->
if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach if (!friendFeedMenuOptions.contains(ruleFeature.ruleType.key)) return@forEach
Logger.debug("${ruleFeature.ruleType.key} ${ruleFeature.getRuleState()}")
val ruleState = ruleFeature.getRuleState() ?: return@forEach val ruleState = ruleFeature.getRuleState() ?: return@forEach
createToggleFeature(viewConsumer, createToggleFeature(viewConsumer,

View File

@ -8,7 +8,6 @@ import android.widget.Button
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ScrollView import android.widget.ScrollView
import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader import me.rhunk.snapenhance.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.ui.ViewAppearanceHelper.applyTheme import me.rhunk.snapenhance.ui.ViewAppearanceHelper.applyTheme
import me.rhunk.snapenhance.ui.menu.AbstractMenu import me.rhunk.snapenhance.ui.menu.AbstractMenu
@ -76,7 +75,7 @@ class OperaContextActionMenu : AbstractMenu() {
linearLayout.addView(button) linearLayout.addView(button)
(childView as ViewGroup).addView(linearLayout, 0) (childView as ViewGroup).addView(linearLayout, 0)
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.xposedLog(e) context.log.error("Error while injecting OperaContextActionMenu", e)
} }
} }
} }

View File

@ -2,15 +2,13 @@ package me.rhunk.snapenhance.ui.menu.impl
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.View import android.view.View
import android.widget.Button
import me.rhunk.snapenhance.ui.ViewAppearanceHelper
import me.rhunk.snapenhance.ui.menu.AbstractMenu import me.rhunk.snapenhance.ui.menu.AbstractMenu
class SettingsMenu : AbstractMenu() { class SettingsMenu : AbstractMenu() {
//TODO: quick settings //TODO: quick settings
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
fun inject(viewModel: View, addView: (View) -> Unit) { fun inject(viewModel: View, addView: (View) -> Unit) {
val actions = context.actionManager.getActions().map { /*val actions = context.actionManager.getActions().map {
Pair(it) { Pair(it) {
val button = Button(viewModel.context) val button = Button(viewModel.context)
button.text = context.translation[it.nameKey] button.text = context.translation[it.nameKey]
@ -25,6 +23,6 @@ class SettingsMenu : AbstractMenu() {
actions.forEach { actions.forEach {
addView(it.second()) addView(it.second())
} }*/
} }
} }

View File

@ -23,7 +23,7 @@ object SQLiteDatabaseHelper {
if (newColumns.isEmpty()) return@forEach if (newColumns.isEmpty()) return@forEach
Logger.log("Schema for table $tableName has changed") Logger.directDebug("Schema for table $tableName has changed")
sqLiteDatabase.execSQL("DROP TABLE $tableName") sqLiteDatabase.execSQL("DROP TABLE $tableName")
sqLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS $tableName (${columns.joinToString(", ")})") sqLiteDatabase.execSQL("CREATE TABLE IF NOT EXISTS $tableName (${columns.joinToString(", ")})")
} }

View File

@ -37,7 +37,7 @@ class HttpServer(
} }
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
Logger.debug("starting http server on port $port") Logger.directDebug("starting http server on port $port")
serverSocket = ServerSocket(port) serverSocket = ServerSocket(port)
callback(this@HttpServer) callback(this@HttpServer)
while (!serverSocket!!.isClosed) { while (!serverSocket!!.isClosed) {
@ -48,21 +48,21 @@ class HttpServer(
handleRequest(socket) handleRequest(socket)
timeoutJob = launch { timeoutJob = launch {
delay(timeout.toLong()) delay(timeout.toLong())
Logger.debug("http server closed due to timeout") Logger.directDebug("http server closed due to timeout")
runCatching { runCatching {
socketJob?.cancel() socketJob?.cancel()
socket.close() socket.close()
serverSocket?.close() serverSocket?.close()
}.onFailure { }.onFailure {
Logger.error(it) Logger.directError("failed to close socket", it)
} }
} }
} }
} catch (e: SocketException) { } catch (e: SocketException) {
Logger.debug("http server timed out") Logger.directDebug("http server timed out")
break; break;
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.error("failed to handle request", e) Logger.directError("failed to handle request", e)
} }
} }
}.also { socketJob = it } }.also { socketJob = it }
@ -90,13 +90,13 @@ class HttpServer(
outputStream.close() outputStream.close()
socket.close() socket.close()
}.onFailure { }.onFailure {
Logger.error("failed to close socket", it) Logger.directError("failed to close socket", it)
} }
} }
val parse = StringTokenizer(line) val parse = StringTokenizer(line)
val method = parse.nextToken().uppercase(Locale.getDefault()) val method = parse.nextToken().uppercase(Locale.getDefault())
var fileRequested = parse.nextToken().lowercase(Locale.getDefault()) var fileRequested = parse.nextToken().lowercase(Locale.getDefault())
Logger.debug("[http-server:${port}] $method $fileRequested") Logger.directDebug("[http-server:${port}] $method $fileRequested")
if (method != "GET") { if (method != "GET") {
with(writer) { with(writer) {

View File

@ -44,7 +44,7 @@ object RemoteMediaResolver {
okHttpClient.newCall(request).execute().use { response -> okHttpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
Logger.log("Unexpected code $response") Logger.directDebug("Unexpected code $response")
return null return null
} }
return ByteArrayInputStream(response.body.bytes()) return ByteArrayInputStream(response.body.bytes())

View File

@ -9,16 +9,15 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import me.rhunk.snapenhance.Logger
import me.rhunk.snapenhance.ModContext import me.rhunk.snapenhance.ModContext
import me.rhunk.snapenhance.core.BuildConfig import me.rhunk.snapenhance.core.BuildConfig
import me.rhunk.snapenhance.core.database.objects.FriendFeedEntry
import me.rhunk.snapenhance.core.database.objects.FriendInfo
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.data.FileType import me.rhunk.snapenhance.data.FileType
import me.rhunk.snapenhance.data.MediaReferenceType import me.rhunk.snapenhance.data.MediaReferenceType
import me.rhunk.snapenhance.data.wrapper.impl.Message import me.rhunk.snapenhance.data.wrapper.impl.Message
import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID import me.rhunk.snapenhance.data.wrapper.impl.SnapUUID
import me.rhunk.snapenhance.database.objects.FriendFeedEntry
import me.rhunk.snapenhance.database.objects.FriendInfo
import me.rhunk.snapenhance.util.protobuf.ProtoReader import me.rhunk.snapenhance.util.protobuf.ProtoReader
import me.rhunk.snapenhance.util.snap.EncryptionHelper import me.rhunk.snapenhance.util.snap.EncryptionHelper
import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper import me.rhunk.snapenhance.util.snap.MediaDownloaderHelper
@ -138,7 +137,7 @@ class MessageExporter(
} }
}.onFailure { }.onFailure {
printLog("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}") printLog("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}")
Logger.error("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}", it) context.log.error("failed to download media for ${message.messageDescriptor.conversationId}_${message.orderKey}", it)
} }
} }
} }
@ -219,7 +218,7 @@ class MessageExporter(
} }
}.onFailure { }.onFailure {
printLog("failed to read template from apk") printLog("failed to read template from apk")
Logger.error("failed to read template from apk", it) context.log.error("failed to read template from apk", it)
} }
output.write("</html>".toByteArray()) output.write("</html>".toByteArray())

View File

@ -4,9 +4,9 @@ import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.FFmpegSession import com.arthenica.ffmpegkit.FFmpegSession
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import me.rhunk.snapenhance.Constants import me.rhunk.snapenhance.Constants
import me.rhunk.snapenhance.core.download.data.SplitMediaAssetType
import me.rhunk.snapenhance.data.ContentType import me.rhunk.snapenhance.data.ContentType
import me.rhunk.snapenhance.data.FileType import me.rhunk.snapenhance.data.FileType
import me.rhunk.snapenhance.download.data.SplitMediaAssetType
import me.rhunk.snapenhance.util.download.RemoteMediaResolver import me.rhunk.snapenhance.util.download.RemoteMediaResolver
import me.rhunk.snapenhance.util.protobuf.ProtoReader import me.rhunk.snapenhance.util.protobuf.ProtoReader
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream