From e7fe5075fb486428a529ddfae7160ac6c7b66e68 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Mon, 22 Jul 2024 19:48:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(core):=20=E7=BB=84=E4=BB=B6=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E4=B8=8B=E6=94=AF=E6=8C=81=20`TelegramBotCommands`?= =?UTF-8?q?=E3=80=81`TelegramBotCommand`=E3=80=81`TelegramBotCommandsUpdat?= =?UTF-8?q?er`=20=E7=AD=89=E4=B8=8E=20bot=20commands=20=E7=9B=B8=E5=85=B3?= =?UTF-8?q?=E7=9A=84API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- simbot-component-telegram-api/supports.md | 2 +- .../telegram/core/bot/TelegramBot.kt | 40 ++++++- .../core/bot/command/TelegramBotCommands.kt | 108 ++++++++++++++++++ .../bot/command/TelegramBotCommandsUpdater.kt | 82 +++++++++++++ .../core/bot/internal/TelegramBotImpl.kt | 28 +++++ .../command/TelegramBotCommandsImpl.kt | 87 ++++++++++++++ .../command/TelegramBotCommandsUpdaterImpl.kt | 60 ++++++++++ 7 files changed, 403 insertions(+), 4 deletions(-) create mode 100644 simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommands.kt create mode 100644 simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommandsUpdater.kt create mode 100644 simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsImpl.kt create mode 100644 simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsUpdaterImpl.kt diff --git a/simbot-component-telegram-api/supports.md b/simbot-component-telegram-api/supports.md index a4322d4..bb5ea7f 100644 --- a/simbot-component-telegram-api/supports.md +++ b/simbot-component-telegram-api/supports.md @@ -40,4 +40,4 @@ - [x] [SetWebhookApi](src/commonMain/kotlin/love/forte/simbot/telegram/api/update/SetWebhookApi.kt) - [user](src/commonMain/kotlin/love/forte/simbot/telegram/api/user) - [x] [GetUserProfilePhotosApi](src/commonMain/kotlin/love/forte/simbot/telegram/api/user/GetUserProfilePhotosApi.kt) -- [ ] **Others aren’t listed** +- [ ] **Others not listed** diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/TelegramBot.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/TelegramBot.kt index ab5fcb8..fd7fd8b 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/TelegramBot.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/TelegramBot.kt @@ -23,9 +23,12 @@ import love.forte.simbot.bot.GroupRelation import love.forte.simbot.bot.GuildRelation import love.forte.simbot.common.id.ID import love.forte.simbot.common.id.LongID.Companion.ID +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommands +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommandsUpdater import love.forte.simbot.component.telegram.core.component.TelegramComponent import love.forte.simbot.suspendrunner.ST import love.forte.simbot.telegram.api.update.Update +import love.forte.simbot.telegram.type.BotCommandScope import love.forte.simbot.telegram.type.User import kotlin.coroutines.CoroutineContext @@ -98,14 +101,45 @@ public interface TelegramBot : Bot { source.pushUpdate(update, raw) } + /** + * Fetches a [TelegramBotCommands] with [scope] and [languageCode]. + * + * @param scope Scope of bot commands. + * @param languageCode A two-letter ISO 639-1 language code of commands. + */ + @ST + public suspend fun commands(scope: BotCommandScope?, languageCode: String?): TelegramBotCommands + + /** + * Fetches a [TelegramBotCommands]. + */ + @ST + public suspend fun commands(): TelegramBotCommands = + commands(scope = null, languageCode = null) + + /** + * An updater for set commands. + * + * @see TelegramBotCommandsUpdater + */ + public val commandsUpdater: TelegramBotCommandsUpdater override val groupRelation: GroupRelation? - get() = null // TODO? + get() = null override val guildRelation: GuildRelation? - get() = null // TODO? + get() = null override val contactRelation: ContactRelation? - get() = null // TODO? + get() = null } + +/** + * Update bot commands by [TelegramBot.commandsUpdater] + * + * @see TelegramBot.commandsUpdater + */ +public suspend inline fun TelegramBot.updateCommands( + block: TelegramBotCommandsUpdater.() -> Unit +): TelegramBotCommands = commandsUpdater.also(block).update() diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommands.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommands.kt new file mode 100644 index 0000000..7328e57 --- /dev/null +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommands.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-telegram. + * + * simbot-component-telegram is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-telegram is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-telegram. + * If not, see . + */ + +package love.forte.simbot.component.telegram.core.bot.command + +import love.forte.simbot.ability.DeleteOption +import love.forte.simbot.ability.DeleteSupport +import love.forte.simbot.component.telegram.core.bot.TelegramBot +import love.forte.simbot.telegram.type.BotCommand +import love.forte.simbot.telegram.type.BotCommandScope +import kotlin.jvm.JvmSynthetic + + +/** + * A set of Telegram's [BotCommand]s + * + * @author ForteScarlet + */ +public interface TelegramBotCommands : DeleteSupport, Iterable { + /** + * The list of [TelegramBotCommand] + */ + public val values: List + + /** + * Iterator of [values]. + */ + override fun iterator(): Iterator = + values.iterator() + + /** + * A command scope used in the query. + */ + public val scope: BotCommandScope? + + /** + * A two-letter ISO 639-1 language code used in the query. + */ + public val languageCode: String? + + /** + * Delete this set of commands with [scope] and [languageCode]. + */ + @JvmSynthetic + override suspend fun delete(vararg options: DeleteOption) + + /** + * Get a [TelegramBotCommandsUpdater] with [scope] and [languageCode] + * for update commands. + * + * @see TelegramBotCommandsUpdater + */ + public val updater: TelegramBotCommandsUpdater +} + +/** + * Update bot commands by [TelegramBotCommands.updater] + * + * @see TelegramBotCommands.updater + */ +public suspend inline fun TelegramBotCommands.update( + block: TelegramBotCommandsUpdater.() -> Unit +): TelegramBotCommands = updater.also(block).update() + +/** + * A [BotCommand] of [TelegramBotCommands] from [TelegramBot.commands]. + * + * @see TelegramBotCommands + * @see BotCommand + */ +public interface TelegramBotCommand { + /** + * The source type [BotCommand] + */ + public val source: BotCommand + + /** + * Text of the command. + * + * @see BotCommand.command + */ + public val command: String + get() = source.command + + /** + * Description of the command. + * + * @see BotCommand.description + */ + public val description: String + get() = source.description +} + + diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommandsUpdater.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommandsUpdater.kt new file mode 100644 index 0000000..9a040eb --- /dev/null +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/command/TelegramBotCommandsUpdater.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-telegram. + * + * simbot-component-telegram is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-telegram is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-telegram. + * If not, see . + */ + +package love.forte.simbot.component.telegram.core.bot.command + +import love.forte.simbot.suspendrunner.ST +import love.forte.simbot.telegram.type.BotCommand +import love.forte.simbot.telegram.type.BotCommandScope + + +/** + * An updater for set bot commands. + * + * @author ForteScarlet + */ +public interface TelegramBotCommandsUpdater { + /** + * the [BotCommandScope] + */ + public var scope: BotCommandScope? + + /** + * the [languageCode] + */ + public var languageCode: String? + + /** + * Commands for update. + */ + public var commands: MutableList + + /** + * @see scope + */ + public fun scope(scope: BotCommandScope?): TelegramBotCommandsUpdater = apply { + this.scope = scope + } + + /** + * @see languageCode + */ + public fun languageCode(languageCode: String?): TelegramBotCommandsUpdater = apply { + this.languageCode = languageCode + } + + /** + * Add a command into [commands] + * @see commands + */ + public fun addCommand(command: BotCommand): TelegramBotCommandsUpdater = apply { + commands.add(command) + } + + /** + * Add a command into [commands] + * @see commands + */ + public fun addCommand(command: String, description: String): TelegramBotCommandsUpdater = + addCommand(BotCommand(command, description)) + + /** + * Execute setting and a new [TelegramBotCommands] that + * values **directly** based on new commands is returned. + * + */ + @ST + public suspend fun update(): TelegramBotCommands +} diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/TelegramBotImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/TelegramBotImpl.kt index 2285037..fbdf92f 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/TelegramBotImpl.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/TelegramBotImpl.kt @@ -31,6 +31,10 @@ import love.forte.simbot.common.id.toLongOrNull import love.forte.simbot.component.telegram.core.bot.StdlibBot import love.forte.simbot.component.telegram.core.bot.TelegramBot import love.forte.simbot.component.telegram.core.bot.TelegramBotConfiguration +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommands +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommandsUpdater +import love.forte.simbot.component.telegram.core.bot.internal.command.TelegramBotCommandsUpdaterImpl +import love.forte.simbot.component.telegram.core.bot.internal.command.toTGCommands import love.forte.simbot.component.telegram.core.component.TelegramComponent import love.forte.simbot.component.telegram.core.event.TelegramUnsupportedEvent import love.forte.simbot.component.telegram.core.event.internal.TelegramChannelMessageEventImpl @@ -42,6 +46,7 @@ import love.forte.simbot.event.EventDispatcher import love.forte.simbot.event.onEachError import love.forte.simbot.logger.LoggerFactory import love.forte.simbot.telegram.api.bot.GetMeApi +import love.forte.simbot.telegram.api.bot.command.GetMyCommandsApi import love.forte.simbot.telegram.api.update.SuspendableUpdateDivider import love.forte.simbot.telegram.api.update.Update import love.forte.simbot.telegram.stdlib.bot.SubscribeSequence @@ -111,9 +116,32 @@ internal class TelegramBotImpl( isStarted = true } } + + override suspend fun commands(scope: BotCommandScope?, languageCode: String?): TelegramBotCommands { + val commands = GetMyCommandsApi.create( + scope = scope, + languageCode = languageCode + ).requestDataBy(source) + + return commands.toTGCommands( + bot = this, + scope = scope, + languageCode = languageCode + ) + } + + override val commandsUpdater: TelegramBotCommandsUpdater + get() = TelegramBotCommandsUpdaterImpl( + bot = this, + scope = null, + languageCode = null + ) } + + + @OptIn(FragileSimbotAPI::class) internal fun subscribeInternalProcessor( bot: TelegramBotImpl, diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsImpl.kt new file mode 100644 index 0000000..d680ae7 --- /dev/null +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsImpl.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-telegram. + * + * simbot-component-telegram is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-telegram is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-telegram. + * If not, see . + */ + +package love.forte.simbot.component.telegram.core.bot.internal.command + +import love.forte.simbot.ability.DeleteFailureException +import love.forte.simbot.ability.DeleteOption +import love.forte.simbot.ability.StandardDeleteOption +import love.forte.simbot.component.telegram.core.bot.TelegramBot +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommand +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommands +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommandsUpdater +import love.forte.simbot.telegram.api.bot.command.DeleteMyCommandsApi +import love.forte.simbot.telegram.stdlib.bot.requestDataBy +import love.forte.simbot.telegram.type.BotCommand +import love.forte.simbot.telegram.type.BotCommandScope + +internal class TelegramBotCommandsImpl( + private val bot: TelegramBot, + override val values: List, + override val scope: BotCommandScope?, + override val languageCode: String?, +) : TelegramBotCommands { + override suspend fun delete(vararg options: DeleteOption) { + runCatching { + DeleteMyCommandsApi.create( + scope = scope, + languageCode = languageCode + ).requestDataBy(bot.source) + }.onFailure { err -> + if (StandardDeleteOption.IGNORE_ON_FAILURE !in options) { + throw DeleteFailureException(err.message, err) + } + } + } + + override val updater: TelegramBotCommandsUpdater + get() = TelegramBotCommandsUpdaterImpl( + bot = bot, + scope = scope, + languageCode = languageCode + ) + + override fun toString(): String = + "TelegramBotCommands(scope=$scope, languageCode=$languageCode, values=$values)" +} + +internal fun Iterable.toTGCommands( + bot: TelegramBot, + scope: BotCommandScope?, + languageCode: String?, +): TelegramBotCommandsImpl = + TelegramBotCommandsImpl( + bot, + this.map { it.toTGCommand() }, + scope, + languageCode + ) + +/** + * + * @author ForteScarlet + */ +internal class TelegramBotCommandImpl( + override val source: BotCommand +) : TelegramBotCommand { + override fun toString(): String { + return "TelegramBotCommand(source=$source)" + } +} + +internal fun BotCommand.toTGCommand(): TelegramBotCommandImpl = + TelegramBotCommandImpl(this) diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsUpdaterImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsUpdaterImpl.kt new file mode 100644 index 0000000..26d71ac --- /dev/null +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/bot/internal/command/TelegramBotCommandsUpdaterImpl.kt @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024. ForteScarlet. + * + * This file is part of simbot-component-telegram. + * + * simbot-component-telegram is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Foundation, + * either version 3 of the License, or (at your option) any later version. + * + * simbot-component-telegram is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License along with simbot-component-telegram. + * If not, see . + */ + +package love.forte.simbot.component.telegram.core.bot.internal.command + +import love.forte.simbot.component.telegram.core.bot.TelegramBot +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommands +import love.forte.simbot.component.telegram.core.bot.command.TelegramBotCommandsUpdater +import love.forte.simbot.telegram.api.bot.command.SetMyCommandsApi +import love.forte.simbot.telegram.stdlib.bot.requestDataBy +import love.forte.simbot.telegram.type.BotCommand +import love.forte.simbot.telegram.type.BotCommandScope + + +/** + * + * @author ForteScarlet + */ +internal class TelegramBotCommandsUpdaterImpl( + private val bot: TelegramBot, + override var scope: BotCommandScope?, + override var languageCode: String?, + override var commands: MutableList = mutableListOf(), +) : TelegramBotCommandsUpdater { + override suspend fun update(): TelegramBotCommands { + val commands = commands.toList() + val scope = scope + val languageCode = languageCode + + val updated = SetMyCommandsApi.create( + commands, + scope, + languageCode + ).requestDataBy(bot.source) + + check(updated) { + "The SetMyCommandsApi request returned `false`" + } + + return commands.toTGCommands( + bot = bot, + scope = scope, + languageCode = languageCode + ) + } +}