mirror of
https://github.com/rhunk/SnapEnhance.git
synced 2025-06-13 05:37:48 +02:00
feat: scripting base
This commit is contained in:
@ -3,7 +3,9 @@ package me.rhunk.snapenhance
|
||||
import android.content.SharedPreferences
|
||||
import android.util.Log
|
||||
import com.google.gson.GsonBuilder
|
||||
import me.rhunk.snapenhance.core.LogLevel
|
||||
import me.rhunk.snapenhance.core.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.core.logger.LogChannel
|
||||
import me.rhunk.snapenhance.core.logger.LogLevel
|
||||
import java.io.File
|
||||
import java.io.OutputStream
|
||||
import java.io.RandomAccessFile
|
||||
@ -100,7 +102,7 @@ class LogReader(
|
||||
|
||||
class LogManager(
|
||||
private val remoteSideContext: RemoteSideContext
|
||||
) {
|
||||
): AbstractLogger(LogChannel.MANAGER) {
|
||||
companion object {
|
||||
private const val TAG = "SnapEnhanceManager"
|
||||
private val LOG_LIFETIME = 24.hours
|
||||
@ -171,32 +173,32 @@ class LogManager(
|
||||
lineAddListener = { line -> it.incrementLineCount(); onAddLine(line) }
|
||||
}
|
||||
|
||||
fun debug(message: Any?, tag: String = TAG) {
|
||||
override fun debug(message: Any?, tag: String) {
|
||||
internalLog(tag, LogLevel.DEBUG, message)
|
||||
}
|
||||
|
||||
fun error(message: Any?, tag: String = TAG) {
|
||||
override fun error(message: Any?, tag: String) {
|
||||
internalLog(tag, LogLevel.ERROR, message)
|
||||
}
|
||||
|
||||
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||
override fun error(message: Any?, throwable: Throwable, tag: String) {
|
||||
internalLog(tag, LogLevel.ERROR, message)
|
||||
internalLog(tag, LogLevel.ERROR, throwable.stackTraceToString())
|
||||
}
|
||||
|
||||
fun info(message: Any?, tag: String = TAG) {
|
||||
override fun info(message: Any?, tag: String) {
|
||||
internalLog(tag, LogLevel.INFO, message)
|
||||
}
|
||||
|
||||
fun verbose(message: Any?, tag: String = TAG) {
|
||||
override fun verbose(message: Any?, tag: String) {
|
||||
internalLog(tag, LogLevel.VERBOSE, message)
|
||||
}
|
||||
|
||||
fun warn(message: Any?, tag: String = TAG) {
|
||||
override fun warn(message: Any?, tag: String) {
|
||||
internalLog(tag, LogLevel.WARN, message)
|
||||
}
|
||||
|
||||
fun assert(message: Any?, tag: String = TAG) {
|
||||
override fun assert(message: Any?, tag: String) {
|
||||
internalLog(tag, LogLevel.ASSERT, message)
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import me.rhunk.snapenhance.core.config.ModConfig
|
||||
import me.rhunk.snapenhance.download.DownloadTaskManager
|
||||
import me.rhunk.snapenhance.messaging.ModDatabase
|
||||
import me.rhunk.snapenhance.messaging.StreaksReminder
|
||||
import me.rhunk.snapenhance.scripting.RemoteScriptManager
|
||||
import me.rhunk.snapenhance.ui.manager.MainActivity
|
||||
import me.rhunk.snapenhance.ui.manager.data.InstallationSummary
|
||||
import me.rhunk.snapenhance.ui.manager.data.ModInfo
|
||||
@ -54,6 +55,7 @@ class RemoteSideContext(
|
||||
val modDatabase = ModDatabase(this)
|
||||
val streaksReminder = StreaksReminder(this)
|
||||
val log = LogManager(this)
|
||||
val scriptManager = RemoteScriptManager(this)
|
||||
|
||||
//used to load bitmoji selfies and download previews
|
||||
val imageLoader by lazy {
|
||||
@ -75,6 +77,7 @@ class RemoteSideContext(
|
||||
val coroutineScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
fun reload() {
|
||||
log.verbose("Loading RemoteSideContext")
|
||||
runCatching {
|
||||
config.loadFromContext(androidContext)
|
||||
translation.apply {
|
||||
@ -88,6 +91,7 @@ class RemoteSideContext(
|
||||
downloadTaskManager.init(androidContext)
|
||||
modDatabase.init()
|
||||
streaksReminder.init()
|
||||
scriptManager.init()
|
||||
}.onFailure {
|
||||
log.error("Failed to load RemoteSideContext", it)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package me.rhunk.snapenhance.bridge
|
||||
import android.app.Service
|
||||
import android.content.Intent
|
||||
import android.os.IBinder
|
||||
import me.rhunk.snapenhance.core.LogLevel
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.SharedContextHolder
|
||||
import me.rhunk.snapenhance.core.bridge.types.BridgeFileType
|
||||
@ -11,10 +10,11 @@ import me.rhunk.snapenhance.core.bridge.types.FileActionType
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.LocaleWrapper
|
||||
import me.rhunk.snapenhance.core.bridge.wrapper.MessageLoggerWrapper
|
||||
import me.rhunk.snapenhance.core.database.objects.FriendInfo
|
||||
import me.rhunk.snapenhance.core.logger.LogLevel
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingFriendInfo
|
||||
import me.rhunk.snapenhance.core.messaging.MessagingGroupInfo
|
||||
import me.rhunk.snapenhance.download.DownloadProcessor
|
||||
import me.rhunk.snapenhance.core.util.SerializableDataObject
|
||||
import me.rhunk.snapenhance.download.DownloadProcessor
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class BridgeService : Service() {
|
||||
@ -168,5 +168,7 @@ class BridgeService : Service() {
|
||||
groups.map { SerializableDataObject.fromJson<MessagingGroupInfo>(it) }
|
||||
)
|
||||
}
|
||||
|
||||
override fun getScriptingInterface() = remoteSideContext.scriptManager
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import com.arthenica.ffmpegkit.FFmpegKit
|
||||
import com.arthenica.ffmpegkit.FFmpegSession
|
||||
import com.arthenica.ffmpegkit.Level
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import me.rhunk.snapenhance.core.LogLevel
|
||||
import me.rhunk.snapenhance.core.logger.LogLevel
|
||||
import me.rhunk.snapenhance.LogManager
|
||||
import me.rhunk.snapenhance.core.Logger
|
||||
import me.rhunk.snapenhance.core.config.impl.DownloaderConfig
|
||||
|
@ -0,0 +1,43 @@
|
||||
package me.rhunk.snapenhance.scripting
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.bridge.scripting.IScripting
|
||||
import me.rhunk.snapenhance.bridge.scripting.ReloadListener
|
||||
|
||||
class RemoteScriptManager(
|
||||
private val context: RemoteSideContext,
|
||||
) : IScripting.Stub() {
|
||||
private val scriptRuntime = ScriptRuntime(context.log)
|
||||
|
||||
private fun getScriptFolder()
|
||||
= DocumentFile.fromTreeUri(context.androidContext, Uri.parse(context.config.root.scripting.moduleFolder.get()))
|
||||
private fun hasHotReload() = context.config.root.scripting.hotReload.get()
|
||||
|
||||
private val reloadListeners = mutableListOf<ReloadListener>()
|
||||
|
||||
fun init() {
|
||||
enabledScriptPaths.forEach { path ->
|
||||
val content = getScriptContent(path)
|
||||
scriptRuntime.load(path, content)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEnabledScriptPaths(): List<String> {
|
||||
val folder = getScriptFolder() ?: return emptyList()
|
||||
return folder.listFiles().filter { it.name?.endsWith(".js") ?: false }.map { it.name!! }
|
||||
}
|
||||
|
||||
override fun getScriptContent(path: String): String {
|
||||
val folder = getScriptFolder() ?: return ""
|
||||
val file = folder.findFile(path) ?: return ""
|
||||
return context.androidContext.contentResolver.openInputStream(file.uri)?.use {
|
||||
it.readBytes().toString(Charsets.UTF_8)
|
||||
} ?: ""
|
||||
}
|
||||
|
||||
override fun registerReloadListener(listener: ReloadListener) {
|
||||
reloadListeners.add(listener)
|
||||
}
|
||||
}
|
@ -2,8 +2,8 @@ package me.rhunk.snapenhance.ui.manager
|
||||
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.DataObject
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Extension
|
||||
import androidx.compose.material.icons.filled.Group
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Stars
|
||||
@ -13,10 +13,10 @@ import androidx.navigation.NavController
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.compose.composable
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.ui.manager.sections.home.HomeSection
|
||||
import me.rhunk.snapenhance.ui.manager.sections.NotImplemented
|
||||
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.home.HomeSection
|
||||
import me.rhunk.snapenhance.ui.manager.sections.social.SocialSection
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@ -45,9 +45,9 @@ enum class EnumSection(
|
||||
icon = Icons.Filled.Group,
|
||||
section = SocialSection::class
|
||||
),
|
||||
PLUGINS(
|
||||
route = "plugins",
|
||||
icon = Icons.Filled.Extension
|
||||
SCRIPTS(
|
||||
route = "scripts",
|
||||
icon = Icons.Filled.DataObject
|
||||
);
|
||||
|
||||
companion object {
|
||||
|
@ -53,8 +53,8 @@ import androidx.compose.ui.window.Dialog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import me.rhunk.snapenhance.Constants
|
||||
import me.rhunk.snapenhance.core.LogChannels
|
||||
import me.rhunk.snapenhance.core.LogLevel
|
||||
import me.rhunk.snapenhance.core.logger.LogChannel
|
||||
import me.rhunk.snapenhance.core.logger.LogLevel
|
||||
import me.rhunk.snapenhance.LogReader
|
||||
import me.rhunk.snapenhance.RemoteSideContext
|
||||
import me.rhunk.snapenhance.action.EnumAction
|
||||
@ -169,7 +169,7 @@ class HomeSubSection(
|
||||
)
|
||||
|
||||
Text(
|
||||
text = LogChannels.fromChannel(line.tag)?.shortName ?: line.tag,
|
||||
text = LogChannel.fromChannel(line.tag)?.shortName ?: line.tag,
|
||||
modifier = Modifier.padding(start = 4.dp),
|
||||
fontWeight = FontWeight.Light,
|
||||
fontSize = 10.sp,
|
||||
|
@ -39,6 +39,7 @@ dependencies {
|
||||
implementation(libs.gson)
|
||||
implementation(libs.okhttp)
|
||||
implementation(libs.androidx.documentfile)
|
||||
implementation(libs.rhino)
|
||||
|
||||
implementation(project(":mapper"))
|
||||
implementation(project(":native"))
|
||||
|
@ -3,6 +3,7 @@ package me.rhunk.snapenhance.bridge;
|
||||
import java.util.List;
|
||||
import me.rhunk.snapenhance.bridge.DownloadCallback;
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback;
|
||||
import me.rhunk.snapenhance.bridge.scripting.IScripting;
|
||||
|
||||
interface BridgeInterface {
|
||||
/**
|
||||
@ -85,4 +86,6 @@ interface BridgeInterface {
|
||||
* @param friends list of friends (MessagingFriendInfo as json string)
|
||||
*/
|
||||
oneway void passGroupsAndFriends(in List<String> groups, in List<String> friends);
|
||||
|
||||
IScripting getScriptingInterface();
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package me.rhunk.snapenhance.bridge.scripting;
|
||||
|
||||
import me.rhunk.snapenhance.bridge.scripting.ReloadListener;
|
||||
|
||||
interface IScripting {
|
||||
List<String> getEnabledScriptPaths();
|
||||
|
||||
String getScriptContent(String path);
|
||||
|
||||
void registerReloadListener(ReloadListener listener);
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
package me.rhunk.snapenhance.bridge.scripting;
|
||||
|
||||
oneway interface ReloadListener {
|
||||
void reloadScript(String path, String content);
|
||||
}
|
@ -28,7 +28,7 @@
|
||||
"home_debug": "Debug",
|
||||
"home_logs": "Logs",
|
||||
"social": "Social",
|
||||
"plugins": "Plugins"
|
||||
"scripts": "Scripts"
|
||||
},
|
||||
"sections": {
|
||||
"home": {
|
||||
@ -521,6 +521,20 @@
|
||||
"description": "Enables unreleased/beta Snapchat Plus features\nMight not work on older Snapchat versions"
|
||||
}
|
||||
}
|
||||
},
|
||||
"scripting": {
|
||||
"name": "Scripting",
|
||||
"description": "Run custom scripts to extend SnapEnhance",
|
||||
"properties": {
|
||||
"module_folder": {
|
||||
"name": "Module Folder",
|
||||
"description": "The folder where the scripts are located"
|
||||
},
|
||||
"hot_reload": {
|
||||
"name": "Hot Reload",
|
||||
"description": "Automatically reloads scripts when they change"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
|
@ -25,6 +25,7 @@ import me.rhunk.snapenhance.manager.impl.ActionManager
|
||||
import me.rhunk.snapenhance.manager.impl.FeatureManager
|
||||
import me.rhunk.snapenhance.nativelib.NativeConfig
|
||||
import me.rhunk.snapenhance.nativelib.NativeLib
|
||||
import me.rhunk.snapenhance.scripting.ScriptRuntime
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.reflect.KClass
|
||||
@ -59,6 +60,7 @@ class ModContext {
|
||||
val messageSender = MessageSender(this)
|
||||
val classCache get() = SnapEnhance.classCache
|
||||
val resources: Resources get() = androidContext.resources
|
||||
val scriptRuntime by lazy { ScriptRuntime(log) }
|
||||
|
||||
fun <T : Feature> feature(featureClass: KClass<T>): T {
|
||||
return features.get(featureClass)!!
|
||||
|
@ -7,6 +7,7 @@ import android.content.pm.PackageManager
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import me.rhunk.snapenhance.bridge.SyncCallback
|
||||
import me.rhunk.snapenhance.bridge.scripting.ReloadListener
|
||||
import me.rhunk.snapenhance.core.BuildConfig
|
||||
import me.rhunk.snapenhance.core.Logger
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
@ -113,6 +114,23 @@ class SnapEnhance {
|
||||
if (!mappings.isMappingsLoaded()) return
|
||||
features.init()
|
||||
syncRemote()
|
||||
|
||||
bridgeClient.getScriptingInterface().apply {
|
||||
registerReloadListener(object: ReloadListener.Stub() {
|
||||
override fun reloadScript(path: String, content: String) {
|
||||
scriptRuntime.reload(path, content)
|
||||
}
|
||||
})
|
||||
|
||||
enabledScriptPaths.forEach { path ->
|
||||
runCatching {
|
||||
scriptRuntime.load(path, getScriptContent(path))
|
||||
}.onFailure {
|
||||
log.error("Failed to load script $path", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}.also { time ->
|
||||
appContext.log.verbose("init took $time")
|
||||
|
@ -4,54 +4,17 @@ import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import de.robv.android.xposed.XposedBridge
|
||||
import me.rhunk.snapenhance.core.bridge.BridgeClient
|
||||
import me.rhunk.snapenhance.core.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.core.logger.LogChannel
|
||||
import me.rhunk.snapenhance.hook.HookStage
|
||||
import me.rhunk.snapenhance.hook.hook
|
||||
|
||||
enum class LogLevel(
|
||||
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);
|
||||
|
||||
companion object {
|
||||
fun fromLetter(letter: String): LogLevel? {
|
||||
return values().find { it.letter == letter }
|
||||
}
|
||||
|
||||
fun fromShortName(shortName: String): LogLevel? {
|
||||
return values().find { it.shortName == shortName }
|
||||
}
|
||||
|
||||
fun fromPriority(priority: Int): LogLevel? {
|
||||
return values().find { it.priority == priority }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class LogChannels(val channel: String, val shortName: String) {
|
||||
CORE("SnapEnhanceCore", "core"),
|
||||
NATIVE("SnapEnhanceNative", "native"),
|
||||
MANAGER("SnapEnhanceManager", "manager"),
|
||||
XPOSED("LSPosed-Bridge", "xposed");
|
||||
|
||||
companion object {
|
||||
fun fromChannel(channel: String): LogChannels? {
|
||||
return values().find { it.channel == channel }
|
||||
}
|
||||
}
|
||||
}
|
||||
import me.rhunk.snapenhance.core.logger.LogLevel
|
||||
|
||||
|
||||
@SuppressLint("PrivateApi")
|
||||
class Logger(
|
||||
private val bridgeClient: BridgeClient
|
||||
) {
|
||||
): AbstractLogger(LogChannel.CORE) {
|
||||
companion object {
|
||||
private const val TAG = "SnapEnhanceCore"
|
||||
|
||||
@ -104,20 +67,20 @@ class Logger(
|
||||
}
|
||||
}
|
||||
|
||||
fun debug(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.DEBUG, message)
|
||||
override fun debug(message: Any?, tag: String) = internalLog(tag, LogLevel.DEBUG, message)
|
||||
|
||||
fun error(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.ERROR, message)
|
||||
override fun error(message: Any?, tag: String) = internalLog(tag, LogLevel.ERROR, message)
|
||||
|
||||
fun error(message: Any?, throwable: Throwable, tag: String = TAG) {
|
||||
override fun error(message: Any?, throwable: Throwable, tag: String) {
|
||||
internalLog(tag, LogLevel.ERROR, message)
|
||||
internalLog(tag, LogLevel.ERROR, throwable.stackTraceToString())
|
||||
}
|
||||
|
||||
fun info(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.INFO, message)
|
||||
override fun info(message: Any?, tag: String) = internalLog(tag, LogLevel.INFO, message)
|
||||
|
||||
fun verbose(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.VERBOSE, message)
|
||||
override fun verbose(message: Any?, tag: String) = internalLog(tag, LogLevel.VERBOSE, message)
|
||||
|
||||
fun warn(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.WARN, message)
|
||||
override fun warn(message: Any?, tag: String) = internalLog(tag, LogLevel.WARN, message)
|
||||
|
||||
fun assert(message: Any?, tag: String = TAG) = internalLog(tag, LogLevel.ASSERT, message)
|
||||
override fun assert(message: Any?, tag: String) = internalLog(tag, LogLevel.ASSERT, message)
|
||||
}
|
@ -148,4 +148,6 @@ class BridgeClient(
|
||||
|
||||
fun setRule(targetUuid: String, type: MessagingRuleType, state: Boolean)
|
||||
= service.setRule(targetUuid, type.key, state)
|
||||
|
||||
fun getScriptingInterface() = service.getScriptingInterface()
|
||||
}
|
||||
|
@ -14,4 +14,5 @@ class RootConfig : ConfigContainer() {
|
||||
val experimental = container("experimental", Experimental()) {
|
||||
icon = "Science"; addNotices(FeatureNotice.UNSTABLE)
|
||||
}
|
||||
val scripting = container("scripting", Scripting()) { icon = "DataObject" }
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package me.rhunk.snapenhance.core.config.impl
|
||||
|
||||
import me.rhunk.snapenhance.core.config.ConfigContainer
|
||||
import me.rhunk.snapenhance.core.config.ConfigFlag
|
||||
|
||||
class Scripting : ConfigContainer() {
|
||||
val moduleFolder = string("module_folder", "modules") { addFlags(ConfigFlag.FOLDER) }
|
||||
val hotReload = boolean("hot_reload", false)
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package me.rhunk.snapenhance.core.logger
|
||||
|
||||
abstract class AbstractLogger(
|
||||
logChannel: LogChannel,
|
||||
) {
|
||||
private val TAG = logChannel.shortName
|
||||
|
||||
|
||||
open fun debug(message: Any?, tag: String = TAG) {}
|
||||
|
||||
open fun error(message: Any?, tag: String = TAG) {}
|
||||
|
||||
open fun error(message: Any?, throwable: Throwable, tag: String = TAG) {}
|
||||
|
||||
open fun info(message: Any?, tag: String = TAG) {}
|
||||
|
||||
open fun verbose(message: Any?, tag: String = TAG) {}
|
||||
|
||||
open fun warn(message: Any?, tag: String = TAG) {}
|
||||
|
||||
open fun assert(message: Any?, tag: String = TAG) {}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package me.rhunk.snapenhance.core.logger
|
||||
|
||||
enum class LogChannel(
|
||||
val channel: String,
|
||||
val shortName: String
|
||||
) {
|
||||
CORE("SnapEnhanceCore", "core"),
|
||||
NATIVE("SnapEnhanceNative", "native"),
|
||||
MANAGER("SnapEnhanceManager", "manager"),
|
||||
XPOSED("LSPosed-Bridge", "xposed");
|
||||
|
||||
companion object {
|
||||
fun fromChannel(channel: String): LogChannel? {
|
||||
return values().find { it.channel == channel }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package me.rhunk.snapenhance.core.logger
|
||||
|
||||
import android.util.Log
|
||||
|
||||
enum class LogLevel(
|
||||
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);
|
||||
|
||||
companion object {
|
||||
fun fromLetter(letter: String): LogLevel? {
|
||||
return values().find { it.letter == letter }
|
||||
}
|
||||
|
||||
fun fromShortName(shortName: String): LogLevel? {
|
||||
return values().find { it.shortName == shortName }
|
||||
}
|
||||
|
||||
fun fromPriority(priority: Int): LogLevel? {
|
||||
return values().find { it.priority == priority }
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package me.rhunk.snapenhance.scripting
|
||||
|
||||
import me.rhunk.snapenhance.core.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.scripting.type.ModuleInfo
|
||||
import org.mozilla.javascript.Context
|
||||
import org.mozilla.javascript.FunctionObject
|
||||
import org.mozilla.javascript.ScriptableObject
|
||||
|
||||
class JSModule(
|
||||
val moduleInfo: ModuleInfo,
|
||||
val content: String,
|
||||
) {
|
||||
lateinit var logger: AbstractLogger
|
||||
private lateinit var scope: ScriptableObject
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun logDebug(message: String) {
|
||||
println(message)
|
||||
}
|
||||
}
|
||||
|
||||
fun load() {
|
||||
val context = Context.enter()
|
||||
context.optimizationLevel = -1
|
||||
scope = context.initSafeStandardObjects()
|
||||
scope.putConst("module", scope, moduleInfo)
|
||||
|
||||
scope.putConst("logDebug", scope,
|
||||
FunctionObject("logDebug", JSModule::class.java.getDeclaredMethod("logDebug", String::class.java), scope)
|
||||
)
|
||||
|
||||
context.evaluateString(scope, content, moduleInfo.name, 1, null)
|
||||
}
|
||||
|
||||
fun unload() {
|
||||
val context = Context.enter()
|
||||
context.evaluateString(scope, "if (typeof module.onUnload === 'function') module.onUnload();", "onUnload", 1, null)
|
||||
Context.exit()
|
||||
}
|
||||
|
||||
fun callOnCoreLoad() {
|
||||
val context = Context.enter()
|
||||
context.evaluateString(scope, "if (typeof module.onCoreLoad === 'function') module.onCoreLoad();", "onCoreLoad", 1, null)
|
||||
Context.exit()
|
||||
}
|
||||
|
||||
fun callOnManagerLoad() {
|
||||
val context = Context.enter()
|
||||
context.evaluateString(scope, "if (typeof module.onManagerLoad === 'function') module.onManagerLoad();", "onManagerLoad", 1, null)
|
||||
Context.exit()
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package me.rhunk.snapenhance.scripting
|
||||
|
||||
import me.rhunk.snapenhance.core.logger.AbstractLogger
|
||||
import me.rhunk.snapenhance.scripting.type.ModuleInfo
|
||||
import java.io.BufferedReader
|
||||
import java.io.ByteArrayInputStream
|
||||
|
||||
class ScriptRuntime(
|
||||
private val logger: AbstractLogger,
|
||||
) {
|
||||
private val modules = mutableMapOf<String, JSModule>()
|
||||
|
||||
private fun readModuleInfo(reader: BufferedReader): ModuleInfo {
|
||||
val header = reader.readLine()
|
||||
if (!header.startsWith("// ==SE_module==")) {
|
||||
throw Exception("Invalid module header")
|
||||
}
|
||||
|
||||
val properties = mutableMapOf<String, String>()
|
||||
while (true) {
|
||||
val line = reader.readLine()
|
||||
if (line.startsWith("// ==/SE_module==")) {
|
||||
break
|
||||
}
|
||||
val split = line.replaceFirst("//", "").split(":")
|
||||
if (split.size != 2) {
|
||||
throw Exception("Invalid module property")
|
||||
}
|
||||
properties[split[0].trim()] = split[1].trim()
|
||||
}
|
||||
|
||||
return ModuleInfo(
|
||||
name = properties["name"] ?: throw Exception("Missing module name"),
|
||||
version = properties["version"] ?: throw Exception("Missing module version"),
|
||||
description = properties["description"],
|
||||
author = properties["author"],
|
||||
minSnapchatVersion = properties["minSnapchatVersion"]?.toLong(),
|
||||
minSEVersion = properties["minSEVersion"]?.toLong(),
|
||||
grantPermissions = properties["permissions"]?.split(",")?.map { it.trim() },
|
||||
)
|
||||
}
|
||||
|
||||
fun reload(path: String, content: String) {
|
||||
unload(path)
|
||||
load(path, content)
|
||||
}
|
||||
|
||||
private fun unload(path: String) {
|
||||
val module = modules[path] ?: return
|
||||
module.unload()
|
||||
module.load()
|
||||
modules.remove(path)
|
||||
}
|
||||
|
||||
fun load(path: String, content: String): JSModule? {
|
||||
logger.info("Loading module $path")
|
||||
return runCatching {
|
||||
JSModule(
|
||||
moduleInfo = readModuleInfo(ByteArrayInputStream(content.toByteArray(Charsets.UTF_8)).bufferedReader()),
|
||||
content = content,
|
||||
).apply {
|
||||
logger = this@ScriptRuntime.logger
|
||||
load()
|
||||
modules[path] = this
|
||||
}
|
||||
}.onFailure {
|
||||
logger.error("Failed to load module $path", it)
|
||||
}.getOrNull()
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package me.rhunk.snapenhance.scripting.type
|
||||
|
||||
data class ModuleInfo(
|
||||
val name: String,
|
||||
val version: String,
|
||||
val description: String? = null,
|
||||
val author: String? = null,
|
||||
val minSnapchatVersion: Long? = null,
|
||||
val minSEVersion: Long? = null,
|
||||
val grantPermissions: List<String>? = null,
|
||||
)
|
@ -18,6 +18,7 @@ dexlib2 = "2.5.2"
|
||||
androidx-documentfile = "1.1.0-alpha01"
|
||||
activity-ktx = "1.7.2"
|
||||
material3 = "1.1.1"
|
||||
rhino = "1.7.14"
|
||||
|
||||
|
||||
[libraries]
|
||||
@ -39,6 +40,7 @@ dexlib2 = { group = "org.smali", name = "dexlib2", version.ref = "dexlib2" }
|
||||
androidx-documentfile = { group = "androidx.documentfile", name = "documentfile", version.ref = "androidx-documentfile" }
|
||||
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activity-ktx" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3", version.ref = "material3" }
|
||||
rhino = { module = "org.mozilla:rhino", version.ref = "rhino" }
|
||||
|
||||
|
||||
[plugins]
|
||||
|
1
scripting/.gitignore
vendored
1
scripting/.gitignore
vendored
@ -1 +0,0 @@
|
||||
build/
|
@ -1,17 +0,0 @@
|
||||
plugins {
|
||||
alias(libs.plugins.androidLibrary)
|
||||
alias(libs.plugins.kotlinAndroid)
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = rootProject.ext["applicationId"].toString() + ".scripting"
|
||||
compileSdk = 34
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":core"))
|
||||
}
|
@ -21,4 +21,3 @@ include(":core")
|
||||
include(":app")
|
||||
include(":mapper")
|
||||
include(":native")
|
||||
include(":scripting")
|
||||
|
Reference in New Issue
Block a user