diff --git a/build.gradle.kts b/build.gradle.kts index d967123..8cf4f9c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ * Copyright (c) 2021-2021 solonovamax * * The file build.gradle.kts is part of PolyhedralBot - * Last modified on 25-10-2021 05:59 p.m. + * Last modified on 25-10-2021 08:19 p.m. * * MIT License * @@ -127,8 +127,10 @@ dependencies { // Kotlin implementation(kotlin("stdlib", KOTLIN_VERSION)) implementation(kotlin("reflect", KOTLIN_VERSION)) // Reflection stuff - implementation(kotlin("script-runtime", KOTLIN_VERSION)) // For executing scripts at runtime - implementation(kotlin("script-util", KOTLIN_VERSION)) + implementation(kotlin("script-util", KOTLIN_VERSION)) // For executing scripts at runtime + implementation(kotlin("script-runtime", KOTLIN_VERSION)) + implementation(kotlin("scripting-jsr223", KOTLIN_VERSION)) + implementation(kotlin("scripting-jvm-host", KOTLIN_VERSION)) implementation(kotlin("compiler-embeddable", KOTLIN_VERSION)) implementation(kotlin("scripting-compiler-embeddable", KOTLIN_VERSION)) // Kotlin Serialization @@ -293,7 +295,7 @@ tasks { it.moduleGroup == "org.jetbrains.exposed" } exclude { - it.moduleName == "kotlin-reflect" + it.moduleName == "kotlin-reflect" || it.moduleName.startsWith("kotlin-script") } exclude { it.moduleGroup == "org.apache.lucene" && (it.moduleName == "lucene-core" || it.moduleName == "lucene-sandbox") diff --git a/src/main/kotlin/ca/solostudios/polybot/PolyBot.kt b/src/main/kotlin/ca/solostudios/polybot/PolyBot.kt index 98eab4b..a6cda72 100644 --- a/src/main/kotlin/ca/solostudios/polybot/PolyBot.kt +++ b/src/main/kotlin/ca/solostudios/polybot/PolyBot.kt @@ -3,7 +3,7 @@ * Copyright (c) 2021-2021 solonovamax * * The file PolyBot.kt is part of PolyhedralBot - * Last modified on 25-10-2021 05:05 p.m. + * Last modified on 25-10-2021 10:16 p.m. * * MIT License * @@ -306,11 +306,11 @@ class PolyBot(val config: PolyConfig, builder: InlineJDABuilder) { } fun userReference(userId: Long): BackedReference { - return BackedReference(userId, { jda.getUserById(it) }, { it?.idLong ?: 0 }) + return BackedReference(userId, { jda.retrieveUserById(it).complete() }, { it?.idLong ?: 0 }) } fun polyUserReference(userId: Long): BackedReference { - return BackedReference(userId, { jda.getUserById(it)?.poly(this) }, { it?.id ?: 0 }) + return BackedReference(userId, { jda.retrieveUserById(it).complete()?.poly(this) }, { it?.id ?: 0 }) } fun user(userId: Long): User? { @@ -323,22 +323,22 @@ class PolyBot(val config: PolyConfig, builder: InlineJDABuilder) { fun memberReference(guildId: Long, userId: Long): BackedReference> { return BackedReference(guildId to userId, - { jda.getGuildById(it.first)?.getMemberById(it.second) }, + { jda.getGuildById(it.first)?.retrieveMemberById(it.second)?.complete() }, { it?.let { it.idLong to it.guild.idLong } ?: (0L to 0L) }) } fun polyMemberReference(guildId: Long, userId: Long): BackedReference> { return BackedReference(guildId to userId, - { jda.getGuildById(it.first)?.getMemberById(it.second)?.poly(this) }, + { jda.getGuildById(it.first)?.retrieveMemberById(it.second)?.complete()?.poly(this) }, { it?.let { it.id to it.guild.id } ?: (0L to 0L) }) } fun member(guildId: Long, userId: Long): Member? { - return jda.getGuildById(guildId)?.getMemberById(userId) + return jda.getGuildById(guildId)?.retrieveMemberById(userId)?.complete() } fun polyMember(guildId: Long, userId: Long): PolyMember? { - return jda.getGuildById(guildId)?.getMemberById(userId)?.poly(this) + return jda.getGuildById(guildId)?.retrieveMemberById(userId)?.complete()?.poly(this) } fun emoteReference(emoteId: Long): BackedReference { diff --git a/src/main/kotlin/ca/solostudios/polybot/commands/BotAdminCommands.kt b/src/main/kotlin/ca/solostudios/polybot/commands/BotAdminCommands.kt index a7d3672..e46de5b 100644 --- a/src/main/kotlin/ca/solostudios/polybot/commands/BotAdminCommands.kt +++ b/src/main/kotlin/ca/solostudios/polybot/commands/BotAdminCommands.kt @@ -3,7 +3,7 @@ * Copyright (c) 2021-2021 solonovamax * * The file BotAdminCommands.kt is part of PolyhedralBot - * Last modified on 25-10-2021 05:05 p.m. + * Last modified on 28-10-2021 07:58 p.m. * * MIT License * @@ -37,16 +37,44 @@ import ca.solostudios.polybot.cloud.commands.annotations.JDAUserPermission import ca.solostudios.polybot.cloud.commands.annotations.PolyCategory import ca.solostudios.polybot.entities.PolyMessage import ca.solostudios.polybot.entities.PolyUser +import cloud.commandframework.annotations.Argument import cloud.commandframework.annotations.CommandDescription import cloud.commandframework.annotations.CommandMethod import cloud.commandframework.annotations.Hidden +import cloud.commandframework.annotations.specifier.Greedy +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import java.time.Duration +import javax.script.ScriptEngineManager +import kotlinx.coroutines.TimeoutCancellationException +import kotlinx.coroutines.time.withTimeout +import net.dv8tion.jda.api.JDA import org.slf4j.kotlin.* +import kotlin.script.experimental.annotations.KotlinScript +import kotlin.script.experimental.api.EvaluationResult +import kotlin.script.experimental.api.ResultWithDiagnostics +import kotlin.script.experimental.api.ScriptAcceptedLocation +import kotlin.script.experimental.api.ScriptCompilationConfiguration +import kotlin.script.experimental.api.ScriptEvaluationConfiguration +import kotlin.script.experimental.api.acceptedLocations +import kotlin.script.experimental.api.defaultImports +import kotlin.script.experimental.api.ide +import kotlin.script.experimental.api.providedProperties +import kotlin.script.experimental.host.toScriptSource +import kotlin.script.experimental.jvm.dependenciesFromClassContext +import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.jvm.updateClasspath +import kotlin.script.experimental.jvm.util.classpathFromClass +import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost +import kotlin.script.experimental.jvmhost.createJvmCompilationConfigurationFromTemplate @Hidden @PolyCommandContainer @PolyCategory(BOT_ADMIN_CATEGORY) class BotAdminCommands(bot: PolyBot) : PolyCommands(bot) { private val logger by getLogger() + private val engine = ScriptEngineManager().getEngineByName("kotlin") + private val scriptingHost = BasicJvmScriptingHost() @CommandName("Shutdown Bot") @CommandMethod("shutdown") @@ -77,4 +105,164 @@ class BotAdminCommands(bot: PolyBot) : PolyCommands(bot) { logger.info { "Update request was triggered by ${author.tag} (${author.id})" } bot.shutdown(ExitCodes.EXIT_CODE_UPDATE) } + + @CommandName("Eval") + @CommandMethod("eval [code]") + @JDAUserPermission(ownerOnly = true) + suspend fun eval(message: PolyMessage, + author: PolyUser, + @Greedy + @Argument(value = "code", description = "Code to evaluate") + code: String) { + assert(author.isOwner) // sanity check + assert(author.id in bot.config.botConfig.ownerIds) + + try { + val result = withTimeout(Duration.ofSeconds(10)) { + val (err, out, result) = captureOutAndErr { + evalString(code) { + providedProperties( + "user" to author, + "message" to message, + "logger" to logger, + "bot" to bot, + "jda" to bot.jda, + ) + } + } + + // result as ResultWithDiagnostics.Success + + + return@withTimeout buildString { + append(result) + + // if (!output.isNullOrEmpty()) { + // val returnValue = result.value.returnValue + // returnValue as ResultValue.Value + // + // returnValue.toString() + // appendLine("\nReturn value:") + // appendLine("```") + // appendLine(output) + // appendLine("```") + // } + + if (out.isNotEmpty()) { + appendLine("\nConsole output:") + appendLine("```") + appendLine(out) + appendLine("```") + } + + if (err.isNotEmpty()) { + appendLine("\nErr output:") + appendLine("```") + appendLine(out) + appendLine("```") + } + } + } + + logger.info { "Result of eval: $result" } + + message.reply("Evaluation completed successfully.") + + if (result.isNotEmpty()) + message.reply(result.takeIf { it.length <= 4000 } ?: result.substring(0, 4000)) + } catch (e: TimeoutCancellationException) { + + } + } + + /** + * From [Jetbrains/kotlin](https://github.com/JetBrains/kotlin/blob/master/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/TestScriptDefinitions.kt#L62). + * + * @param T + * @param source + * @param configure + * @receiver + * @return + */ + private inline fun evalString( + source: String, + noinline configure: ScriptEvaluationConfiguration.Builder.() -> Unit + ): ResultWithDiagnostics { + val actualConfiguration = createJvmCompilationConfigurationFromTemplate() + return scriptingHost.eval(source.toScriptSource(), actualConfiguration, ScriptEvaluationConfiguration(configure)) + } + + @KotlinScript( + displayName = "PolyScript", + compilationConfiguration = KotlinAdminScriptCompilationConfiguration::class, + ) + abstract class KotlinAdminScript + + class KotlinAdminScriptCompilationConfiguration : ScriptCompilationConfiguration( + { + defaultImports(DEFAULT_EVAL_IMPORTS) + + updateClasspath(classpathFromClass()) + + providedProperties( + "bot" to PolyBot::class, + "logger" to KLogger::class, + "jda" to JDA::class, + // "guild" to PolyGuild::class, + // "member" to PolyMember::class, + "user" to PolyUser::class, + "message" to PolyMessage::class + ) + + jvm { + dependenciesFromClassContext(contextClass = PolyBot::class, wholeClasspath = true) + } + ide { + acceptedLocations(ScriptAcceptedLocation.Everywhere) + } + } + ) { + companion object { + val DEFAULT_EVAL_IMPORTS = listOf( + "kotlin.*", + "kotlinx.*", + "kotlinx.coroutines.*", + "ca.solostudios.polybot.*", + "ca.solostudios.polybot.util.*", + "ca.solostudios.polybot.entities.*", + "net.dv8tion.jda.api.*", + "org.slf4j.kotlin.*", + ) + + } + } + + /** + * Taken from [Jetbrains/kotlin](https://github.com/JetBrains/kotlin/blob/master/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt#L519). + * + * @param body + * @receiver + * @return + */ + private fun captureOutAndErr(body: () -> ResultWithDiagnostics): Triple> { + val outStream = ByteArrayOutputStream() + val errStream = ByteArrayOutputStream() + + val prevOut = System.out + val prevErr = System.err + System.setOut(PrintStream(outStream)) + System.setErr(PrintStream(errStream)) + lateinit var res: ResultWithDiagnostics + try { + res = body() + } finally { + System.out.flush() + System.err.flush() + System.setOut(prevOut) + System.setErr(prevErr) + } + + + return Triple(outStream.toString().trim(), errStream.toString().trim(), res) + } } \ No newline at end of file diff --git a/src/main/resources/META-INF/kotlin/script/templates/ca.solostudios.polybot.commands.BotAdminCommands.KotlinAdminScript.classname b/src/main/resources/META-INF/kotlin/script/templates/ca.solostudios.polybot.commands.BotAdminCommands.KotlinAdminScript.classname new file mode 100644 index 0000000..e69de29