mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Add Kotlin plugin system (SDK, host, hooks, settings UI)
Implement exteraGram-style plugin architecture for Android and desktop: - plugins/sdk: BasePlugin API, hook types, settings DSL - plugins/host: PluginEngine, typed send hooks, FeatureGate, HookRegistry - JVM ByteBuddy and Android Pine hook backends - DevServer on port 42690 (JVM) - Plugins settings screens and PluginUiHost bulletin overlay - hello_world sample plugin packaged as .fcplugin - ProGuard keepnames for ru.fromchat.** with shrinking enabled Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
@@ -0,0 +1,62 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":plugins:sdk"))
|
||||
}
|
||||
|
||||
tasks.register("packageFcPlugin") {
|
||||
group = "plugins"
|
||||
description = "Build hello_world.fcplugin with desktop JAR"
|
||||
dependsOn(tasks.named("compileKotlin"))
|
||||
doLast {
|
||||
val buildDir = layout.buildDirectory.get().asFile
|
||||
val staging = buildDir.resolve("fcplugin-staging")
|
||||
staging.deleteRecursively()
|
||||
staging.mkdirs()
|
||||
staging.resolve("desktop").mkdirs()
|
||||
staging.resolve("android").mkdirs()
|
||||
|
||||
val jvmClasses = buildDir.resolve("classes/kotlin/main")
|
||||
val jarFile = staging.resolve("desktop/plugin.jar")
|
||||
val jarProcess = ProcessBuilder(
|
||||
"jar",
|
||||
"cf",
|
||||
jarFile.absolutePath,
|
||||
"-C",
|
||||
jvmClasses.absolutePath,
|
||||
".",
|
||||
).inheritIO().start()
|
||||
check(jarProcess.waitFor() == 0) { "jar failed" }
|
||||
|
||||
staging.resolve("manifest.json").writeText(
|
||||
"""
|
||||
{
|
||||
"id": "hello_world",
|
||||
"name": "Hello World",
|
||||
"description": "Rewrites .hello commands into a friendly greeting",
|
||||
"author": "FromChat",
|
||||
"version": "1.0.0",
|
||||
"app_version": ">=1.0.0",
|
||||
"sdk_version": ">=1.0.0",
|
||||
"entry_class": "ru.fromchat.plugins.sample.hello.HelloWorldPlugin",
|
||||
"artifacts": {
|
||||
"android": "android/plugin.dex",
|
||||
"desktop": "desktop/plugin.jar"
|
||||
}
|
||||
}
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
val archive = buildDir.resolve("distributions/hello_world.fcplugin")
|
||||
archive.parentFile.mkdirs()
|
||||
if (archive.exists()) archive.delete()
|
||||
val zipProcess = ProcessBuilder("zip", "-r", archive.absolutePath, ".")
|
||||
.directory(staging)
|
||||
.inheritIO()
|
||||
.start()
|
||||
check(zipProcess.waitFor() == 0) { "zip failed" }
|
||||
println("Created ${archive.absolutePath}")
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
package ru.fromchat.plugins.sample.hello
|
||||
|
||||
import ru.fromchat.plugins.BasePlugin
|
||||
import ru.fromchat.plugins.HookResult
|
||||
import ru.fromchat.plugins.HookStrategy
|
||||
import ru.fromchat.plugins.PluginSetting
|
||||
import ru.fromchat.plugins.SendMessageHookContext
|
||||
|
||||
class HelloWorldPlugin : BasePlugin() {
|
||||
override fun onPluginLoad() {
|
||||
log("Hello World plugin loaded")
|
||||
showBulletin("Hello World plugin is active")
|
||||
addOnSendMessageHook { context ->
|
||||
onSendMessage(context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createSettings(): List<PluginSetting> = listOf(
|
||||
PluginSetting.Header("Hello World"),
|
||||
PluginSetting.Input(
|
||||
key = "template",
|
||||
text = "Greeting template",
|
||||
default = "Hello, {name}!",
|
||||
subtext = "Use {name} for the entered name",
|
||||
),
|
||||
PluginSetting.Text(
|
||||
text = "Send .hello Alice in a chat",
|
||||
subtext = "Example: .hello Alice -> Hello, Alice!",
|
||||
),
|
||||
)
|
||||
|
||||
private fun onSendMessage(context: SendMessageHookContext): HookResult<SendMessageHookContext> {
|
||||
val raw = context.text.trim()
|
||||
if (!raw.startsWith(".hello")) return HookResult()
|
||||
val parts = raw.split(" ", limit = 2)
|
||||
val name = parts.getOrNull(1)?.trim().orEmpty().ifEmpty { "World" }
|
||||
val template = getSettingString("template", "Hello, {name}!")
|
||||
context.text = template.replace("{name}", name)
|
||||
return HookResult(strategy = HookStrategy.MODIFY, value = context)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user