initialization

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

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 :)")
}
}
}