initialization

This commit is contained in:
InsanusMokrassar 2022-07-09 20:49:33 +06:00
parent b36407c7e7
commit dcf4433551
14 changed files with 465 additions and 48 deletions

5
CHANGELOG.md Normal file
View File

@ -0,0 +1,5 @@
# Changelog
## 0.1.0
* Project has been created

View File

@ -6,6 +6,7 @@ buildscript {
dependencies { dependencies {
classpath libs.kotlin.gradle.plugin classpath libs.kotlin.gradle.plugin
classpath libs.kotlin.serialization.plugin classpath libs.kotlin.serialization.plugin
classpath libs.gh.release.plugin
} }
} }
@ -17,6 +18,9 @@ plugins {
project.group = project_group project.group = project_group
project.version = project_version project.version = project_version
apply from: "./publish.gradle"
apply from: "./github_release.gradle"
repositories { repositories {
mavenCentral() mavenCentral()
mavenLocal() mavenLocal()

24
changelog_parser.sh Normal file
View File

@ -0,0 +1,24 @@
#!/bin/bash
function parse() {
version="$1"
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+ $version"`" ]
do
: # do nothing
done
while IFS= read -r line && [ -z "`echo "$line" | grep -e "^#\+"`" ]
do
echo "$line"
done
}
version="$1"
file="$2"
if [ -n "$file" ]; then
parse "$version" < "$file"
else
parse "$version"
fi

30
github_release.gradle Normal file
View File

@ -0,0 +1,30 @@
private String getCurrentVersionChangelog() {
OutputStream changelogDataOS = new ByteArrayOutputStream()
exec {
commandLine 'chmod', "+x", './changelog_parser.sh'
}
exec {
standardOutput = changelogDataOS
commandLine './changelog_parser.sh', "${project.version}", 'CHANGELOG.md'
}
return changelogDataOS.toString().trim()
}
if (new File(projectDir, "secret.gradle").exists()) {
apply from: './secret.gradle'
apply plugin: "com.github.breadmoirai.github-release"
githubRelease {
token "${project.property('GITHUB_RELEASE_TOKEN')}"
owner "InsanusMokrassar"
repo "PlaguBotCommandsPlugin"
tagName "v${project.version}"
releaseName "${project.version}"
targetCommitish "${project.version}"
body getCurrentVersionChangelog()
}
}

View File

@ -3,5 +3,5 @@ org.gradle.parallel=true
kotlin.js.generate.externals=true kotlin.js.generate.externals=true
kotlin.incremental=true kotlin.incremental=true
project_group=plagubot_plugin project_group=dev.inmo
project_version=0.0.1 project_version=0.1.0

View File

@ -13,6 +13,7 @@ kslog = { module = "dev.inmo:kslog", version.ref = "kslog" }
# Libs for classpath # Libs for classpath
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } kotlin-serialization-plugin = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" }
gh-release-plugin = { module = "com.github.breadmoirai:github-release", version.ref = "gh-release" }
[plugins] [plugins]

91
publish.gradle Normal file
View File

@ -0,0 +1,91 @@
apply plugin: 'maven-publish'
task javadocJar(type: Jar) {
from javadoc
classifier = 'javadoc'
}
task sourcesJar(type: Jar) {
from sourceSets.main.allSource
classifier = 'sources'
}
publishing {
publications {
maven(MavenPublication) {
from components.java
artifact javadocJar
artifact sourcesJar
pom {
resolveStrategy = Closure.DELEGATE_FIRST
description = "Plugin for centralized declaration and management of commands inside of plagubot system"
name = "${project.name}"
url = "https://github.com/InsanusMokrassar/PlaguBotCommandsPlugin"
scm {
developerConnection = "scm:git:[fetch=]https://github.com/InsanusMokrassar/PlaguBotCommandsPlugin.git[push=]https://github.com/InsanusMokrassar/PlaguBotCommandsPlugin.git"
url = "https://github.com/InsanusMokrassar/PlaguBotCommandsPlugin.git"
}
developers {
developer {
id = "InsanusMokrassar"
name = "Ovsiannikov Aleksei"
email = "ovsyannikov.alexey95@gmail.com"
}
}
licenses {
license {
name = "MIT License"
url = "https://opensource.org/licenses/MIT"
}
}
}
repositories {
if ((project.hasProperty('GITHUBPACKAGES_USER') || System.getenv('GITHUBPACKAGES_USER') != null) && (project.hasProperty('GITHUBPACKAGES_PASSWORD') || System.getenv('GITHUBPACKAGES_PASSWORD') != null)) {
maven {
name = "GithubPackages"
url = uri("https://maven.pkg.github.com/InsanusMokrassar/PlaguBotCommandsPlugin")
credentials {
username = project.hasProperty('GITHUBPACKAGES_USER') ? project.property('GITHUBPACKAGES_USER') : System.getenv('GITHUBPACKAGES_USER')
password = project.hasProperty('GITHUBPACKAGES_PASSWORD') ? project.property('GITHUBPACKAGES_PASSWORD') : System.getenv('GITHUBPACKAGES_PASSWORD')
}
}
}
if ((project.hasProperty('SONATYPE_USER') || System.getenv('SONATYPE_USER') != null) && (project.hasProperty('SONATYPE_PASSWORD') || System.getenv('SONATYPE_PASSWORD') != null)) {
maven {
name = "sonatype"
url = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = project.hasProperty('SONATYPE_USER') ? project.property('SONATYPE_USER') : System.getenv('SONATYPE_USER')
password = project.hasProperty('SONATYPE_PASSWORD') ? project.property('SONATYPE_PASSWORD') : System.getenv('SONATYPE_PASSWORD')
}
}
}
}
}
}
}
if (project.hasProperty("signing.gnupg.keyName")) {
apply plugin: 'signing'
signing {
useGpgCmd()
sign publishing.publications
}
task signAll {
tasks.withType(Sign).forEach {
dependsOn(it)
}
}
}

1
publish.kpsb Normal file
View File

@ -0,0 +1 @@
{"licenses":[{"id":"MIT","title":"MIT License","url":"https://opensource.org/licenses/MIT"}],"mavenConfig":{"name":"${project.name}","description":"Plugin for centralized declaration and management of commands inside of plagubot system","url":"https://github.com/InsanusMokrassar/PlaguBotCommandsPlugin","vcsUrl":"https://github.com/InsanusMokrassar/PlaguBotCommandsPlugin.git","developers":[{"id":"InsanusMokrassar","name":"Ovsiannikov Aleksei","eMail":"ovsyannikov.alexey95@gmail.com"}],"repositories":[{"name":"GithubPackages","url":"https://maven.pkg.github.com/InsanusMokrassar/PlaguBotCommandsPlugin"},{"name":"sonatype","url":"https://oss.sonatype.org/service/local/staging/deploy/maven2/"}],"gpgSigning":{"type":"dev.inmo.kmppscriptbuilder.core.models.GpgSigning.Optional"}},"type":"JVM"}

View File

@ -1 +1 @@
rootProject.name = 'plagubot_plugin' rootProject.name = 'plagubot.plugins.commands'

View File

@ -0,0 +1,45 @@
package dev.inmo.plagubot.plugins.commands
import dev.inmo.micro_utils.language_codes.IetfLanguageCode
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.commands.BotCommandScope
import kotlinx.serialization.Serializable
/**
* Full info about the command its [key] and the [command] itself
*
* @see full
*/
@Serializable
data class BotCommandFullInfo(
val key: CommandsKeeperKey,
val command: BotCommand
) {
constructor(command: BotCommand) : this(CommandsKeeperKey.DEFAULT, command)
}
fun BotCommand.full(
key: CommandsKeeperKey = CommandsKeeperKey.DEFAULT
) = BotCommandFullInfo(key, this)
fun BotCommand.full(
scope: BotCommandScope
) = full(CommandsKeeperKey(scope))
fun BotCommand.full(
languageCode: String
) = full(CommandsKeeperKey(languageCode = languageCode))
fun BotCommand.full(
languageCode: IetfLanguageCode
) = full(CommandsKeeperKey(BotCommandScope.Default, languageCode = languageCode))
fun BotCommand.full(
scope: BotCommandScope,
languageCode: String
) = full(CommandsKeeperKey(scope, languageCode))
fun BotCommand.full(
scope: BotCommandScope,
languageCode: IetfLanguageCode
) = full(CommandsKeeperKey(scope, languageCode))

View File

@ -0,0 +1,148 @@
package dev.inmo.plagubot.plugins.commands
import dev.inmo.micro_utils.language_codes.IetfLanguageCode
import dev.inmo.tgbotapi.types.BotCommand
import dev.inmo.tgbotapi.types.commands.BotCommandScope
import dev.inmo.tgbotapi.types.commands.BotCommandScopeDefault
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
/**
* In memory commands keeper. Contains all the registered commands inside and can be useful in case you wish to
* [addCommand] or [removeCommand]
*/
class CommandsKeeper(
preset: List<BotCommandFullInfo> = emptyList()
) {
internal val onScopeChanged = MutableSharedFlow<CommandsKeeperKey>()
private val scopesCommands: MutableMap<CommandsKeeperKey, MutableSet<BotCommand>> = preset.groupBy {
it.key
}.mapValues { (_, v) ->
v.map { it.command }.toMutableSet()
}.toMutableMap()
private val changesMutex = Mutex()
suspend fun addCommand(scope: CommandsKeeperKey, command: BotCommand) {
changesMutex.withLock {
val added = scopesCommands.getOrPut(scope) {
mutableSetOf()
}.add(command)
if (added) {
onScopeChanged.emit(scope)
}
}
}
suspend fun addCommand(
scope: BotCommandScope,
command: BotCommand
) = addCommand(
CommandsKeeperKey(scope, null),
command
)
suspend fun addCommand(
languageCode: String,
command: BotCommand
) = addCommand(
CommandsKeeperKey(languageCode = languageCode),
command
)
suspend fun addCommand(
languageCode: IetfLanguageCode,
command: BotCommand
) = addCommand(
CommandsKeeperKey(BotCommandScopeDefault, languageCode),
command
)
suspend fun addCommand(
scope: BotCommandScope,
languageCode: IetfLanguageCode,
command: BotCommand
) = addCommand(
CommandsKeeperKey(scope, languageCode),
command
)
suspend fun addCommand(
scope: BotCommandScope,
languageCode: String,
command: BotCommand
) = addCommand(
CommandsKeeperKey(scope, languageCode),
command
)
suspend fun addCommand(
command: BotCommand
) = addCommand(
CommandsKeeperKey.DEFAULT,
command
)
suspend fun removeCommand(scope: CommandsKeeperKey, command: BotCommand) {
changesMutex.withLock {
val removed = scopesCommands[scope] ?.remove(command) == true
if (removed) {
onScopeChanged.emit(scope)
}
}
}
suspend fun removeCommand(
scope: BotCommandScope,
command: BotCommand
) = removeCommand(
CommandsKeeperKey(scope),
command
)
suspend fun removeCommand(
languageCode: String,
command: BotCommand
) = removeCommand(
CommandsKeeperKey(languageCode = languageCode),
command
)
suspend fun removeCommand(
languageCode: IetfLanguageCode,
command: BotCommand
) = removeCommand(
CommandsKeeperKey(BotCommandScopeDefault, languageCode),
command
)
suspend fun removeCommand(
scope: BotCommandScope,
languageCode: IetfLanguageCode,
command: BotCommand
) = removeCommand(
CommandsKeeperKey(scope, languageCode),
command
)
suspend fun removeCommand(
scope: BotCommandScope,
languageCode: String,
command: BotCommand
) = removeCommand(
CommandsKeeperKey(scope, languageCode),
command
)
suspend fun removeCommand(
command: BotCommand
) = removeCommand(
CommandsKeeperKey.DEFAULT,
command
)
internal fun getCommands(scope: CommandsKeeperKey) = scopesCommands[scope] ?.toList()
internal fun getKeys() = scopesCommands.keys.toList()
}

View File

@ -0,0 +1,35 @@
package dev.inmo.plagubot.plugins.commands
import dev.inmo.micro_utils.language_codes.IetfLanguageCode
import dev.inmo.tgbotapi.types.commands.BotCommandScope
import dev.inmo.tgbotapi.types.commands.BotCommandScopeDefault
import kotlinx.serialization.Serializable
/**
* Full info about the command scope including [BotCommandScope] and its optional language code (see [languageCode] and
* [languageCodeIetf])
*
* @see CommandsKeeperKey.DEFAULT
*/
@Serializable
@JvmInline
value class CommandsKeeperKey(
val key: Pair<BotCommandScope, String?>
) {
val scope: BotCommandScope
get() = key.first
val languageCode: String?
get() = key.second
val languageCodeIetf: IetfLanguageCode?
get() = languageCode ?.let(::IetfLanguageCode)
constructor(scope: BotCommandScope = BotCommandScope.Default, languageCode: String? = null) : this(scope to languageCode)
constructor(scope: BotCommandScope, languageCode: IetfLanguageCode) : this(scope to languageCode.code)
companion object {
/**
* Default realization of [CommandsKeeperKey] with null [languageCode] and [BotCommandScope.Default] [scope]
*/
val DEFAULT = CommandsKeeperKey()
}
}

View File

@ -0,0 +1,78 @@
package dev.inmo.plagubot.plugins.commands
import dev.inmo.kslog.common.*
import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.plagubot.Plugin
import dev.inmo.tgbotapi.extensions.api.bot.*
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.types.BotCommand
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin
import org.koin.core.module.Module
/**
* This plugin has been created for centralized work with commands in context of [Plugin]s system of plagubot. Pass
* [BotCommandFullInfo] in your [Plugin.setupDI] section to declare some command. You may use [CommandsKeeper] for
* flexible setup of commands in runtime.
*/
@Serializable
class CommandsPlugin : Plugin {
private val log = KSLog(logTag)
/**
* Creating [CommandsKeeper] and pass it to the DI. It uses [org.koin.core.scope.Scope.getAll] to get all the
* [BotCommandFullInfo] instances declared in the DI.
*/
override fun Module.setupDI(database: Database, params: JsonObject) {
single { CommandsKeeper(getAll<BotCommandFullInfo>().distinct()) }
}
private suspend fun BehaviourContext.setScopeCommands(key: CommandsKeeperKey, commands: List<BotCommand>?) {
runCatchingSafely {
commands ?.let {
setMyCommands(
commands,
key.scope,
key.languageCode
)
} ?: deleteMyCommands(
key.scope,
key.languageCode
)
}.onFailure {
log.e {
"Unable to ${if (commands == null) "delete commands" else "set new commands (${commands.joinToString { it.command }})"} for key $key"
}
}.onSuccess {
log.i {
"Successfully ${if (commands == null) "deleted commands" else "set new commands (${commands.joinToString { it.command }})"} for key $key"
}
}
}
/**
* Uses [CommandsKeeper] from [koin]. Subscribe on [CommandsKeeper.scopesCommands] to follow changed in scopes and
* take all the available keys in the [CommandsKeeper] and set commands for each key
*/
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
val commandsKeeper = koin.get<CommandsKeeper>()
log.d { "Subscribe to scopes changed flow" }
commandsKeeper.onScopeChanged.subscribeSafelyWithoutExceptions(scope) {
val commands = commandsKeeper.getCommands(it)
setScopeCommands(it, commands)
}
log.d { "Subscribed to scopes changed flow" }
log.d { "Start setup initially passed commands" }
commandsKeeper.getKeys().forEach {
val commands = commandsKeeper.getCommands(it)
log.d { "Start setup initially passed commands for key $it: ${commands ?.joinToString { it.command }}" }
setScopeCommands(it, commandsKeeper.getCommands(it))
}
log.d { "Complete setup initially passed commands" }
}
}

View File

@ -1,45 +0,0 @@
package plagubot_plugin
import dev.inmo.kslog.common.KSLog
import dev.inmo.kslog.common.logTag
import dev.inmo.kslog.common.d
import dev.inmo.kslog.common.dS
import dev.inmo.plagubot.Plugin
import dev.inmo.tgbotapi.extensions.api.bot.getMe
import dev.inmo.tgbotapi.extensions.api.send.reply
import dev.inmo.tgbotapi.extensions.behaviour_builder.BehaviourContext
import dev.inmo.tgbotapi.extensions.behaviour_builder.triggers_handling.onCommand
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin
import org.koin.core.module.Module
// Left empty constructor for the plugin instantiation in bot
@Serializable
class PlaguBotPlugin : Plugin {
private val logger = KSLog(logTag)
@Serializable
data class Config(
val someParam: String = "Default value"
)
override fun Module.setupDI(database: Database, params: JsonObject) {
// Here you may declare any dependencies you need
single {
// Take from config value by field "plugin". Replace to your plugin name and fill Config with your plugin configuration
get<Json>().decodeFromJsonElement(Config.serializer(), params["plugin"] ?: return@single null)
}
}
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {
// Here you may configure the behaviour of your plugin
// All dependencies available in bot can be accessed via koin
logger.d { koin.get<Config>().someParam }
logger.dS { getMe().toString() }
onCommand("hello_world") {
reply(it, "Hello :)")
}
}
}