From 5f1aaa7a6318175ac967f4cbeee3fefc527c449a Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Fri, 12 Apr 2024 00:48:14 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=B6=88=E6=81=AF?= =?UTF-8?q?=E4=BA=8B=E4=BB=B6=E7=9B=B8=E5=85=B3=E7=9A=84=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E7=BB=A7=E6=89=BF=E5=85=B3=E7=B3=BB=E7=BB=93=E6=9E=84=EF=BC=9B?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E9=83=A8=E5=88=86=E5=AF=B9=E5=8F=91=E9=80=81?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E6=B6=88=E6=81=AF=E4=BB=A5=E5=8F=8AMessageEn?= =?UTF-8?q?tity=E7=9A=84=E5=85=83=E7=B4=A0=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish-snapshot.yml | 8 + .../telegram/api/message/SendMessageApi.kt | 30 ++- .../api/message/SendMessageApiTests.kt | 8 +- .../core/actor/TelegramChatGroupActor.kt | 17 +- .../core/event/TelegramMessageEvent.kt | 81 +++---- .../AbstractTelegramGroupMessageEventImpl.kt | 81 +++++++ .../TelegramChannelMessageEventImpl.kt | 56 +---- .../TelegramChatGroupMessageEventImpl.kt | 57 +---- .../TelegramPrivateMessageEventImpl.kt | 3 +- .../TelegramSuperGroupMessageEventImpl.kt | 61 +---- .../core/message/TelegramMessageEntity.kt | 226 ++++++++++++++++++ .../core/message/TelegramMessageResolver.kt | 63 ++--- .../TelegramMessageResultApiElement.kt | 1 - .../core/message/TelegramTextParseMode.kt | 38 +++ .../internal/ReceivingMessageResolvers.kt | 75 +++++- .../SendingMessageStandardResolvers.kt | 80 ++++++- .../core/message/TelegramTextEntitiesTests.kt | 185 ++++++++++++++ .../forte/simbot/telegram/type/Message.kt | 58 ++++- 18 files changed, 853 insertions(+), 275 deletions(-) create mode 100644 simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/AbstractTelegramGroupMessageEventImpl.kt create mode 100644 simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageEntity.kt create mode 100644 simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextParseMode.kt create mode 100644 simbot-component-telegram-core/src/commonTest/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextEntitiesTests.kt diff --git a/.github/workflows/publish-snapshot.yml b/.github/workflows/publish-snapshot.yml index 750fe5d..9425bf9 100644 --- a/.github/workflows/publish-snapshot.yml +++ b/.github/workflows/publish-snapshot.yml @@ -67,6 +67,14 @@ jobs: -Porg.gradle.jvmargs="-Xmx8g -Xms2g -XX:MaxMetaspaceSize=1g -Dfile.encoding=UTF-8" -Porg.gradle.daemon=false + - name: Upload test reports + uses: actions/upload-artifact@v4 + if: ${{ always() }} + with: + name: test-reports-${{ runner.os }} + path: '**/build/reports/tests' + retention-days: 7 + deploy-doc: name: Deploy-doc runs-on: ubuntu-latest diff --git a/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/message/SendMessageApi.kt b/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/message/SendMessageApi.kt index 5eb0a3d..f02b0ec 100644 --- a/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/message/SendMessageApi.kt +++ b/simbot-component-telegram-api/src/commonMain/kotlin/love/forte/simbot/telegram/api/message/SendMessageApi.kt @@ -19,7 +19,6 @@ package love.forte.simbot.telegram.api.message import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import kotlinx.serialization.SerializationStrategy import love.forte.simbot.telegram.api.SimpleBodyTelegramApi import love.forte.simbot.telegram.api.TelegramApiResult import love.forte.simbot.telegram.api.message.ReplyMarkupWrapper.Companion.wrapper @@ -78,9 +77,6 @@ public class SendMessageApi private constructor(body: Body) : SimpleBodyTelegram override val body: Any = body - override val bodySerializationStrategy: SerializationStrategy? - get() = super.bodySerializationStrategy - override val responseDeserializer: DeserializationStrategy get() = Message.serializer() @@ -174,10 +170,13 @@ public class SendMessageApi private constructor(body: Body) : SimpleBodyTelegram * @see Body.text */ public var text: StringBuilder? = null + private set + + public val textOffset: Int + get() = text?.length ?: 0 - public fun text(append: CharSequence) { - val apd = text ?: StringBuilder().also { text = it } - apd.append(append) + public fun appendText(append: CharSequence) { + text?.append(append) ?: StringBuilder(append).also { text = it } } // Optional @@ -202,7 +201,20 @@ public class SendMessageApi private constructor(body: Body) : SimpleBodyTelegram /** * @see Body.entities */ - public var entities: Collection? = null + public var entities: MutableCollection? = null + private set + + /** + * @see Body.entities + */ + public fun addEntity(entity: MessageEntity) { + val c = entities + ?: mutableListOf().also { + entities = it + } + + c.add(entity) + } /** * @see Body.linkPreviewOptions @@ -306,6 +318,6 @@ public inline fun buildSendMessageApi( block: Builder.() -> Unit = {} ): SendMessageApi = buildSendMessageApi { this.chatId = chatId - this.text = StringBuilder(text) + appendText(text) block() } diff --git a/simbot-component-telegram-api/src/commonTest/kotlin/love/forte/simbot/telegram/api/message/SendMessageApiTests.kt b/simbot-component-telegram-api/src/commonTest/kotlin/love/forte/simbot/telegram/api/message/SendMessageApiTests.kt index b9406dd..19c73d6 100644 --- a/simbot-component-telegram-api/src/commonTest/kotlin/love/forte/simbot/telegram/api/message/SendMessageApiTests.kt +++ b/simbot-component-telegram-api/src/commonTest/kotlin/love/forte/simbot/telegram/api/message/SendMessageApiTests.kt @@ -37,7 +37,7 @@ class SendMessageApiTests { with(Telegram.DefaultJson) { val body = SendMessageApi.builder().apply { chatId(1) - text("forte") + appendText("forte") replyMarkup( ForceReply( forceReply = true, @@ -57,7 +57,7 @@ class SendMessageApiTests { with(Telegram.DefaultJson) { val body = SendMessageApi.builder().apply { chatId(1) - text("forte") + appendText("forte") replyMarkup( ReplyKeyboardMarkup( keyboard = listOf(listOf(KeyboardButton(text = "button"))), @@ -80,7 +80,7 @@ class SendMessageApiTests { with(Telegram.DefaultJson) { val body = SendMessageApi.builder().apply { chatId(1) - text("forte") + appendText("forte") replyMarkup( ReplyKeyboardRemove( removeKeyboard = true, @@ -99,7 +99,7 @@ class SendMessageApiTests { with(Telegram.DefaultJson) { val body = SendMessageApi.builder().apply { chatId(1) - text("forte") + appendText("forte") replyMarkup( InlineKeyboardMarkup( inlineKeyboard = listOf(listOf( diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/actor/TelegramChatGroupActor.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/actor/TelegramChatGroupActor.kt index 90b1314..341bb38 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/actor/TelegramChatGroupActor.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/actor/TelegramChatGroupActor.kt @@ -38,13 +38,12 @@ import kotlin.coroutines.CoroutineContext /** + * A type that is aware of [Chat]. * * @author ForteScarlet */ public interface TelegramChatAware { public val source: Chat - - } /** @@ -112,14 +111,22 @@ public interface TelegramChatGroupActor : TelegramChatAware, ChatGroup { override suspend fun send(messageContent: MessageContent): TelegramMessageReceipt } +/** + * A Telegram [Chat] representing a group or a channel. + * + * @see TelegramChatGroup + * @see TelegramChannel + */ +public interface TelegramGroup : TelegramChatGroupActor + +// TODO SuperGroup? /** * A Telegram [Chat] representing a group ([Chat.type] == [ChatType.GROUP] or [ChatType.SUPERGROUP]). * * @author ForteScarlet */ -public interface TelegramChatGroup : TelegramChatGroupActor - +public interface TelegramChatGroup : TelegramGroup /** * @@ -129,4 +136,4 @@ public interface TelegramChatGroup : TelegramChatGroupActor * * @author ForteScarlet */ -public interface TelegramChannel : TelegramChatGroupActor +public interface TelegramChannel : TelegramGroup diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/TelegramMessageEvent.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/TelegramMessageEvent.kt index 963cd8d..02893d3 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/TelegramMessageEvent.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/TelegramMessageEvent.kt @@ -20,10 +20,7 @@ package love.forte.simbot.component.telegram.core.event import love.forte.simbot.common.id.ID import love.forte.simbot.common.id.LongID.Companion.ID import love.forte.simbot.common.time.Timestamp -import love.forte.simbot.component.telegram.core.actor.TelegramChannel -import love.forte.simbot.component.telegram.core.actor.TelegramChatGroup -import love.forte.simbot.component.telegram.core.actor.TelegramContact -import love.forte.simbot.component.telegram.core.actor.TelegramMember +import love.forte.simbot.component.telegram.core.actor.* import love.forte.simbot.component.telegram.core.message.TelegramMessageContent import love.forte.simbot.component.telegram.core.message.TelegramMessageReceipt import love.forte.simbot.component.telegram.core.time.unixDateTimestamp @@ -57,18 +54,22 @@ public interface TelegramMessageRelatedEvent : TelegramEvent, TypeBasedTelegramM public interface TelegramMessageEvent : TelegramMessageRelatedEvent, BasicTelegramMessageEvent /** - * An event about [Message] from a [TelegramChatGroup] (chat.type == `"group"`) + * An event about [Message] from [TelegramChannel] or [TelegramChatGroup]. + * + * @see TelegramChatGroupMessageEvent + * @see TelegramSuperGroupMessageEvent + * @see TelegramChannelMessageEvent * * @author ForteScarlet */ -public interface TelegramChatGroupMessageEvent : TelegramMessageEvent, ChatGroupMessageEvent { +public interface TelegramGroupMessageEvent : TelegramMessageEvent, ChatGroupMessageEvent { override val messageContent: TelegramMessageContent /** - * The [TelegramChatGroup]. + * The [TelegramGroup]. */ @STP - override suspend fun content(): TelegramChatGroup + override suspend fun content(): TelegramGroup /** * The [sender][Message.from]'s [id][User.id] @@ -78,49 +79,51 @@ public interface TelegramChatGroupMessageEvent : TelegramMessageEvent, ChatGroup @STP override suspend fun author(): TelegramMember - // TODO chat group member? + /** + * Reply to this message with [text]. + */ @ST override suspend fun reply(text: String): TelegramMessageReceipt + /** + * Reply to this message with [message]. + */ @ST override suspend fun reply(message: love.forte.simbot.message.Message): TelegramMessageReceipt + /** + * Reply to this message with [messageContent]. + */ @ST override suspend fun reply(messageContent: MessageContent): TelegramMessageReceipt } /** - * An event about [Message] from a [TelegramChatGroup] (chat.type == `"supergroup"`) + * An event about [Message] from a [TelegramChatGroup] (chat.type == `"group"`) * * @author ForteScarlet */ -public interface TelegramSuperGroupMessageEvent : TelegramMessageEvent, ChatGroupMessageEvent { - override val messageContent: TelegramMessageContent - +public interface TelegramChatGroupMessageEvent : TelegramGroupMessageEvent { /** * The [TelegramChatGroup]. */ @STP override suspend fun content(): TelegramChatGroup +} + +/** + * An event about [Message] from a [TelegramChatGroup] (chat.type == `"supergroup"`) + * + * @author ForteScarlet + */ +public interface TelegramSuperGroupMessageEvent : TelegramGroupMessageEvent { /** - * The [sender][Message.from]'s [id][User.id] + * The [TelegramGroup]. */ - override val authorId: ID - get() = sourceContent.from!!.id.ID - @STP - override suspend fun author(): TelegramMember - - @ST - override suspend fun reply(text: String): TelegramMessageReceipt - - @ST - override suspend fun reply(message: love.forte.simbot.message.Message): TelegramMessageReceipt - - @ST - override suspend fun reply(messageContent: MessageContent): TelegramMessageReceipt + override suspend fun content(): TelegramGroup } /** @@ -128,32 +131,12 @@ public interface TelegramSuperGroupMessageEvent : TelegramMessageEvent, ChatGrou * * @author ForteScarlet */ -public interface TelegramChannelMessageEvent : TelegramMessageEvent, ChatGroupMessageEvent { - override val messageContent: TelegramMessageContent - +public interface TelegramChannelMessageEvent : TelegramGroupMessageEvent { /** * The [TelegramChannel]. */ @STP override suspend fun content(): TelegramChannel - - /** - * The [sender][Message.from]'s [id][User.id] - */ - override val authorId: ID - get() = sourceContent.from!!.id.ID - - @STP - override suspend fun author(): TelegramMember - - @ST - override suspend fun reply(text: String): TelegramMessageReceipt - - @ST - override suspend fun reply(message: love.forte.simbot.message.Message): TelegramMessageReceipt - - @ST - override suspend fun reply(messageContent: MessageContent): TelegramMessageReceipt } /** @@ -162,6 +145,8 @@ public interface TelegramChannelMessageEvent : TelegramMessageEvent, ChatGroupMe * @author ForteScarlet */ public interface TelegramPrivateMessageEvent : TelegramMessageEvent, ContactMessageEvent { + override val messageContent: TelegramMessageContent + /** * The [TelegramContact]. */ diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/AbstractTelegramGroupMessageEventImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/AbstractTelegramGroupMessageEventImpl.kt new file mode 100644 index 0000000..c53a137 --- /dev/null +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/AbstractTelegramGroupMessageEventImpl.kt @@ -0,0 +1,81 @@ +/* + * 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.event.internal + +import love.forte.simbot.component.telegram.core.actor.TelegramMember +import love.forte.simbot.component.telegram.core.actor.internal.toTelegramMember +import love.forte.simbot.component.telegram.core.bot.internal.TelegramBotImpl +import love.forte.simbot.component.telegram.core.bot.requestDataBy +import love.forte.simbot.component.telegram.core.event.TelegramGroupMessageEvent +import love.forte.simbot.component.telegram.core.message.TelegramMessageContent +import love.forte.simbot.component.telegram.core.message.TelegramMessageReceipt +import love.forte.simbot.component.telegram.core.message.internal.TelegramMessageContentImpl +import love.forte.simbot.component.telegram.core.message.internal.toTelegramMessageReceipt +import love.forte.simbot.component.telegram.core.message.send +import love.forte.simbot.message.MessageContent +import love.forte.simbot.telegram.api.message.SendMessageApi +import love.forte.simbot.telegram.api.message.buildSendMessageApi +import love.forte.simbot.telegram.type.ChatId +import love.forte.simbot.telegram.type.Message +import love.forte.simbot.telegram.type.ReplyParameters + + +/** + * + * @author ForteScarlet + */ +internal abstract class AbstractTelegramGroupMessageEventImpl( + final override val bot: TelegramBotImpl, + final override val sourceContent: Message +) : TelegramGroupMessageEvent { + override val messageContent: TelegramMessageContent = TelegramMessageContentImpl(bot, sourceContent) + + override suspend fun author(): TelegramMember { + // TODO from!!? check senderChat? + return sourceContent.from!!.toTelegramMember(bot) + } + + override suspend fun reply(text: String): TelegramMessageReceipt { + return buildSendMessageApi(ChatId(sourceContent.chat.id), text) { + replyParameters = ReplyParameters(sourceContent.messageId) + }.requestDataBy(bot).toTelegramMessageReceipt(bot) + } + + override suspend fun reply(message: love.forte.simbot.message.Message): TelegramMessageReceipt { + return bot.send(message, sourceContent.chat.id) { + SendMessageApi.builder().also { + it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) + } + } + } + + override suspend fun reply(messageContent: MessageContent): TelegramMessageReceipt { + return bot.send( + messageContent, + sourceContent.chat.id, + copyApiBlock = { + replyParameters = ReplyParameters(messageId = sourceContent.messageId) + }, + builderFactory = { + SendMessageApi.builder().also { + it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) + } + } + ) + } +} diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChannelMessageEventImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChannelMessageEventImpl.kt index c390151..610b643 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChannelMessageEventImpl.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChannelMessageEventImpl.kt @@ -18,20 +18,10 @@ package love.forte.simbot.component.telegram.core.event.internal import love.forte.simbot.component.telegram.core.actor.TelegramChannel -import love.forte.simbot.component.telegram.core.actor.TelegramMember import love.forte.simbot.component.telegram.core.actor.internal.toTelegramChannel -import love.forte.simbot.component.telegram.core.actor.internal.toTelegramMember import love.forte.simbot.component.telegram.core.bot.internal.TelegramBotImpl import love.forte.simbot.component.telegram.core.event.StdlibEvent -import love.forte.simbot.component.telegram.core.event.TelegramChannelMessageEvent -import love.forte.simbot.component.telegram.core.message.TelegramMessageContent -import love.forte.simbot.component.telegram.core.message.TelegramMessageReceipt -import love.forte.simbot.component.telegram.core.message.internal.TelegramMessageContentImpl -import love.forte.simbot.component.telegram.core.message.send -import love.forte.simbot.message.MessageContent -import love.forte.simbot.telegram.api.message.SendMessageApi import love.forte.simbot.telegram.type.Message -import love.forte.simbot.telegram.type.ReplyParameters /** @@ -39,52 +29,18 @@ import love.forte.simbot.telegram.type.ReplyParameters * @author ForteScarlet */ internal class TelegramChannelMessageEventImpl( - override val bot: TelegramBotImpl, + bot: TelegramBotImpl, override val sourceEvent: StdlibEvent, // 记得确保 chat.type == GROUP - override val sourceContent: Message -) : TelegramChannelMessageEvent { - override val messageContent: TelegramMessageContent = TelegramMessageContentImpl(bot, sourceContent) - - + sourceContent: Message +) : AbstractTelegramGroupMessageEventImpl( + bot, + sourceContent +) { override suspend fun content(): TelegramChannel { return sourceContent.chat.toTelegramChannel(bot) } - override suspend fun author(): TelegramMember { - // TODO check senderChat? - return sourceContent.from!!.toTelegramMember(bot) - } - - override suspend fun reply(text: String): TelegramMessageReceipt { - return bot.send(text, sourceContent.chat.id) { - replyParameters = ReplyParameters(sourceContent.messageId) - } - } - - override suspend fun reply(message: love.forte.simbot.message.Message): TelegramMessageReceipt { - return bot.send(message, sourceContent.chat.id) { - SendMessageApi.builder().also { - it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) - } - } - } - - override suspend fun reply(messageContent: MessageContent): TelegramMessageReceipt { - return bot.send( - messageContent, - sourceContent.chat.id, - copyApiBlock = { - replyParameters = ReplyParameters(messageId = sourceContent.messageId) - }, - builderFactory = { - SendMessageApi.builder().also { - it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) - } - } - ) - } - override fun toString(): String { return "TelegramChatGroupMessageEvent(name=${sourceEvent.name})" } diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChatGroupMessageEventImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChatGroupMessageEventImpl.kt index 5c1629a..db37323 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChatGroupMessageEventImpl.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramChatGroupMessageEventImpl.kt @@ -17,23 +17,13 @@ package love.forte.simbot.component.telegram.core.event.internal +import love.forte.simbot.component.telegram.core.actor.TelegramChatGroup import love.forte.simbot.component.telegram.core.actor.internal.toTelegramChatGroup -import love.forte.simbot.component.telegram.core.actor.internal.toTelegramMember import love.forte.simbot.component.telegram.core.bot.internal.TelegramBotImpl -import love.forte.simbot.component.telegram.core.bot.requestDataBy import love.forte.simbot.component.telegram.core.event.StdlibEvent -import love.forte.simbot.component.telegram.core.event.TelegramChatGroupMessageEvent import love.forte.simbot.component.telegram.core.message.TelegramMessageContent -import love.forte.simbot.component.telegram.core.message.TelegramMessageReceipt import love.forte.simbot.component.telegram.core.message.internal.TelegramMessageContentImpl -import love.forte.simbot.component.telegram.core.message.internal.toTelegramMessageReceipt -import love.forte.simbot.component.telegram.core.message.send -import love.forte.simbot.message.MessageContent -import love.forte.simbot.telegram.api.message.SendMessageApi -import love.forte.simbot.telegram.api.message.buildSendMessageApi -import love.forte.simbot.telegram.type.ChatId import love.forte.simbot.telegram.type.Message -import love.forte.simbot.telegram.type.ReplyParameters /** @@ -41,51 +31,20 @@ import love.forte.simbot.telegram.type.ReplyParameters * @author ForteScarlet */ internal class TelegramChatGroupMessageEventImpl( - override val bot: TelegramBotImpl, + bot: TelegramBotImpl, override val sourceEvent: StdlibEvent, // 记得确保 chat.type == GROUP - override val sourceContent: Message -) : TelegramChatGroupMessageEvent { + sourceContent: Message +) : AbstractTelegramGroupMessageEventImpl( + bot, + sourceContent +) { override val messageContent: TelegramMessageContent = TelegramMessageContentImpl(bot, sourceContent) - override suspend fun content(): love.forte.simbot.component.telegram.core.actor.TelegramChatGroup { + override suspend fun content(): TelegramChatGroup { return sourceContent.chat.toTelegramChatGroup(bot) } - override suspend fun author(): love.forte.simbot.component.telegram.core.actor.TelegramMember { - // TODO from!!? check senderChat? - return sourceContent.from!!.toTelegramMember(bot) - } - - override suspend fun reply(text: String): TelegramMessageReceipt { - return buildSendMessageApi(ChatId(sourceContent.chat.id), text) { - replyParameters = ReplyParameters(sourceContent.messageId) - }.requestDataBy(bot).toTelegramMessageReceipt(bot) - } - - override suspend fun reply(message: love.forte.simbot.message.Message): TelegramMessageReceipt { - return bot.send(message, sourceContent.chat.id) { - SendMessageApi.builder().also { - it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) - } - } - } - - override suspend fun reply(messageContent: MessageContent): TelegramMessageReceipt { - return bot.send( - messageContent, - sourceContent.chat.id, - copyApiBlock = { - replyParameters = ReplyParameters(messageId = sourceContent.messageId) - }, - builderFactory = { - SendMessageApi.builder().also { - it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) - } - } - ) - } - override fun toString(): String { return "TelegramChatGroupMessageEvent(name=${sourceEvent.name})" } diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramPrivateMessageEventImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramPrivateMessageEventImpl.kt index 4bd51bf..6a02935 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramPrivateMessageEventImpl.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramPrivateMessageEventImpl.kt @@ -22,6 +22,7 @@ import love.forte.simbot.component.telegram.core.bot.internal.TelegramBotImpl import love.forte.simbot.component.telegram.core.bot.requestDataBy import love.forte.simbot.component.telegram.core.event.StdlibEvent import love.forte.simbot.component.telegram.core.event.TelegramPrivateMessageEvent +import love.forte.simbot.component.telegram.core.message.TelegramMessageContent import love.forte.simbot.component.telegram.core.message.TelegramMessageReceipt import love.forte.simbot.component.telegram.core.message.internal.TelegramMessageContentImpl import love.forte.simbot.component.telegram.core.message.internal.toTelegramMessageReceipt @@ -43,7 +44,7 @@ internal class TelegramPrivateMessageEventImpl( override val sourceEvent: StdlibEvent, override val sourceContent: Message ) : TelegramPrivateMessageEvent { - override val messageContent: MessageContent = TelegramMessageContentImpl(bot, sourceContent) + override val messageContent: TelegramMessageContent = TelegramMessageContentImpl(bot, sourceContent) override suspend fun content(): love.forte.simbot.component.telegram.core.actor.TelegramContact { return sourceContent.from!!.toTelegramUserContact(bot, sourceContent.chat) diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramSuperGroupMessageEventImpl.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramSuperGroupMessageEventImpl.kt index b33f016..bf343aa 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramSuperGroupMessageEventImpl.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/event/internal/TelegramSuperGroupMessageEventImpl.kt @@ -17,23 +17,11 @@ package love.forte.simbot.component.telegram.core.event.internal +import love.forte.simbot.component.telegram.core.actor.TelegramChatGroup import love.forte.simbot.component.telegram.core.actor.internal.toTelegramChatGroup -import love.forte.simbot.component.telegram.core.actor.internal.toTelegramMember import love.forte.simbot.component.telegram.core.bot.internal.TelegramBotImpl -import love.forte.simbot.component.telegram.core.bot.requestDataBy import love.forte.simbot.component.telegram.core.event.StdlibEvent -import love.forte.simbot.component.telegram.core.event.TelegramChatGroupMessageEvent -import love.forte.simbot.component.telegram.core.message.TelegramMessageContent -import love.forte.simbot.component.telegram.core.message.TelegramMessageReceipt -import love.forte.simbot.component.telegram.core.message.internal.TelegramMessageContentImpl -import love.forte.simbot.component.telegram.core.message.internal.toTelegramMessageReceipt -import love.forte.simbot.component.telegram.core.message.send -import love.forte.simbot.message.MessageContent -import love.forte.simbot.telegram.api.message.SendMessageApi -import love.forte.simbot.telegram.api.message.buildSendMessageApi -import love.forte.simbot.telegram.type.ChatId import love.forte.simbot.telegram.type.Message -import love.forte.simbot.telegram.type.ReplyParameters /** @@ -41,51 +29,18 @@ import love.forte.simbot.telegram.type.ReplyParameters * @author ForteScarlet */ internal class TelegramSuperGroupMessageEventImpl( - override val bot: TelegramBotImpl, + bot: TelegramBotImpl, override val sourceEvent: StdlibEvent, // 记得确保 chat.type == SUPERGROUP - override val sourceContent: Message -) : TelegramChatGroupMessageEvent { - override val messageContent: TelegramMessageContent = TelegramMessageContentImpl(bot, sourceContent) - - override suspend fun content(): love.forte.simbot.component.telegram.core.actor.TelegramChatGroup { + sourceContent: Message +) : AbstractTelegramGroupMessageEventImpl( + bot, + sourceContent +) { + override suspend fun content(): TelegramChatGroup { return sourceContent.chat.toTelegramChatGroup(bot) } - override suspend fun author(): love.forte.simbot.component.telegram.core.actor.TelegramMember { - // TODO from!!? check senderChat? - return sourceContent.from!!.toTelegramMember(bot) - } - - override suspend fun reply(text: String): TelegramMessageReceipt { - return buildSendMessageApi(ChatId(sourceContent.chat.id), text) { - replyParameters = ReplyParameters(sourceContent.messageId) - }.requestDataBy(bot).toTelegramMessageReceipt(bot) - } - - override suspend fun reply(message: love.forte.simbot.message.Message): TelegramMessageReceipt { - return bot.send(message, sourceContent.chat.id) { - SendMessageApi.builder().also { - it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) - } - } - } - - override suspend fun reply(messageContent: MessageContent): TelegramMessageReceipt { - return bot.send( - messageContent, - sourceContent.chat.id, - copyApiBlock = { - replyParameters = ReplyParameters(messageId = sourceContent.messageId) - }, - builderFactory = { - SendMessageApi.builder().also { - it.replyParameters = ReplyParameters(messageId = sourceContent.messageId) - } - } - ) - } - override fun toString(): String { return "TelegramSuperGroupMessageEvent(name=${sourceEvent.name})" } diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageEntity.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageEntity.kt new file mode 100644 index 0000000..517743e --- /dev/null +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageEntity.kt @@ -0,0 +1,226 @@ +/* + * 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.message + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import love.forte.simbot.common.id.ID +import love.forte.simbot.message.MentionMessage +import love.forte.simbot.message.PlainText +import love.forte.simbot.telegram.type.Message +import love.forte.simbot.telegram.type.MessageEntity +import love.forte.simbot.telegram.type.MessageEntityType +import love.forte.simbot.telegram.type.User +import kotlin.jvm.JvmOverloads +import kotlin.jvm.JvmStatic + +/** + * A [TelegramMessageElement] for [MessageEntity]. + * + * ## 发送 + * 当发送一个或多个 [TelegramMessageEntity] 时, + * 它们会自动聚合为 `text` 和 `entities`。 + * + * 例如: + * + * ```Kotlin + * send( + * createTextLink("GitHub", "https://github.com/") + + * ",Hello,".toText() + + * create("1=1", MessageEntityType.CODE) + * ) + * ``` + * 发送时会产生大概这样的 [Message]: + * ```json + * { + * ... + * "text": "GitHub,Hello,1=1", + * "entities": [ + * { + * "type": "text_link", + * "offset": 0, + * "length": 6 + * }, + * { + * "type": "code", + * "offset": 13, + * "length": 3 + * } + * ] + * } + * ``` + * + */ +@Serializable +@SerialName("telegram.m.message_entity") +public sealed class TelegramMessageEntity : TelegramMessageElement, PlainText { + abstract override val text: String + public abstract val type: String + + /** + * The [MessageEntity] (from received message event). + * `null` if not from event. + */ + public abstract val sourceEntity: MessageEntity? + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is TelegramMessageEntity) return false + + if (text != other.text) return false + if (type != other.type) return false + if (sourceEntity != other.sourceEntity) return false + + return true + } + + override fun hashCode(): Int { + var result = text.hashCode() + result = 31 * result + type.hashCode() + result = 31 * result + (sourceEntity?.hashCode() ?: 0) + return result + } + + override fun toString(): String { + return "TelegramMessageEntity(" + + "text='$text', " + + "type='$type', " + + "sourceEntity=$sourceEntity)" + } + + public companion object { + /** + * Create [Simple]. + */ + @JvmStatic + public fun create(text: String, type: String): TelegramMessageEntity = + Simple(text, type, null) + + /** + * Create [Simple]. + */ + @JvmStatic + public fun create(text: String, type: MessageEntityType): TelegramMessageEntity = + create(text, type.typeValue) + + /** + * Create [TextLink]. + */ + @JvmStatic + @JvmOverloads + public fun createTextLink(text: String, url: String? = null): TelegramMessageEntity = + TextLink(text, url, null) + + /** + * Create [TextMention]. + */ + @JvmStatic + @JvmOverloads + public fun createTextMention(text: String, user: User? = null): TelegramMessageEntity = + TextMention(text, user, null) + + /** + * Create [Pre]. + */ + @JvmStatic + @JvmOverloads + public fun createPre(text: String, language: String? = null): TelegramMessageEntity = + Pre(text, language, null) + + /** + * Create [CustomEmoji]. + */ + @JvmStatic + @JvmOverloads + public fun createCustomEmoji(text: String, customEmojiId: ID? = null): TelegramMessageEntity = + CustomEmoji(text, customEmojiId, null) + } + + /** + * A simple implementation for [TelegramMessageEntity]. + */ + @Serializable + @SerialName("telegram.m.message_entity.simple") + public class Simple internal constructor( + override val text: String, + override val type: String, + override val sourceEntity: MessageEntity? + ) : TelegramMessageEntity() + + /** + * An implementation with type [MessageEntityType.TEXT_LINK] + * for [TelegramMessageEntity]. + */ + @Serializable + @SerialName("telegram.m.message_entity.text_link") + public class TextLink internal constructor( + override val text: String, + public val url: String?, + override val sourceEntity: MessageEntity? + ) : TelegramMessageEntity() { + override val type: String + get() = MessageEntityType.TEXT_LINK.typeValue + } + + /** + * An implementation with type [MessageEntityType.TEXT_MENTION] + * for [TelegramMessageEntity]. + */ + @Serializable + @SerialName("telegram.m.message_entity.text_mention") + public class TextMention internal constructor( + override val text: String, + public val user: User?, + override val sourceEntity: MessageEntity? + ) : TelegramMessageEntity(), MentionMessage { + override val type: String + get() = MessageEntityType.TEXT_MENTION.typeValue + } + + /** + * An implementation with type [MessageEntityType.PRE] + * for [TelegramMessageEntity]. + */ + @Serializable + @SerialName("telegram.m.message_entity.pre") + public class Pre internal constructor( + override val text: String, + public val language: String?, + override val sourceEntity: MessageEntity? + ) : TelegramMessageEntity() { + override val type: String + get() = MessageEntityType.PRE.typeValue + } + + /** + * An implementation with type [MessageEntityType.PRE] + * for [TelegramMessageEntity]. + */ + @Serializable + @SerialName("telegram.m.message_entity.custom_emoji") + public class CustomEmoji internal constructor( + override val text: String, + public val customEmojiId: ID?, + override val sourceEntity: MessageEntity? + ) : TelegramMessageEntity() { + override val type: String + get() = MessageEntityType.CUSTOM_EMOJI.typeValue + } + + +} diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResolver.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResolver.kt index 660ff98..bc664e8 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResolver.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResolver.kt @@ -21,8 +21,8 @@ package love.forte.simbot.component.telegram.core.message import love.forte.simbot.component.telegram.core.bot.internal.TelegramBotImpl import love.forte.simbot.component.telegram.core.bot.requestDataBy -import love.forte.simbot.component.telegram.core.message.internal.PlainTextResolver import love.forte.simbot.component.telegram.core.message.internal.TelegramAggregatedMessageIdReceiptImpl +import love.forte.simbot.component.telegram.core.message.internal.TextSendingResolver import love.forte.simbot.component.telegram.core.message.internal.toTelegramMessageReceipt import love.forte.simbot.message.* import love.forte.simbot.telegram.api.TelegramApi @@ -80,7 +80,13 @@ internal suspend fun TelegramBotImpl.send( chatId: Long, builderFactory: BuilderFactory = DefaultBuilderFactory ): TelegramMessageReceipt { - val funcList = message.resolve(builderFactory) + val funcList = message.resolve { + builderFactory().also { + if (it.chatId == null) { + it.chatId = ChatId(chatId) + } + } + } fun toReceipt(result: Any): TelegramSingleMessageReceipt { return when (result) { @@ -192,7 +198,7 @@ internal fun interface SendingMessageResolver { } private val sendingResolvers = listOf( - PlainTextResolver, + TextSendingResolver, TelegramMessageResultApiElementSendingResolver, ) @@ -205,7 +211,14 @@ internal typealias SendingMessageResolvedFunction = () -> TelegramApi<*> // * internal class SendingMessageResolverContext( private val builderFactory: BuilderFactory, ) { - val apiStacks = mutableListOf() // TelegramApi? + private val apiStacks = mutableListOf() // TelegramApi? + + /** + * SendMessageApi 应当尽可能只有最终的一个。 + * 如果有冲突的属性,后者覆盖前者。 + * 如果有其他API,则根据 [_builder] 是否初始化为准, + * 追加到之前或之后。 + */ private var _builder: SendMessageApi.Builder? = null fun addToStackMsg(api: () -> TelegramApi) { @@ -216,50 +229,16 @@ internal class SendingMessageResolverContext( apiStacks.add(api) } - val builderOrNull: SendMessageApi.Builder? - get() = _builder - - /** - * 如果当前builder存在,记录并消除。 - */ - fun archiveCurrent() { - _builder?.also { b -> - apiStacks.add { b.build() } - _builder = null - } - } - private val builderOrNew: SendMessageApi.Builder - get() = _builder ?: builderFactory().also { _builder = it } + get() = _builder ?: builderFactory().also { + _builder = it + addToStackMsg { it.build() } + } val builder: SendMessageApi.Builder get() = builderOrNew - fun newBuilder(): SendMessageApi.Builder { - archiveCurrent() - return builderFactory().also { _builder = it } - } - - inline fun builderOrNew(createNew: (SendMessageApi.Builder) -> Boolean): SendMessageApi.Builder { - val current = builderOrNew - return if (createNew(current)) { - newBuilder() - } else { - current - } - } - - inline fun builderOrNullOrNew(createNew: (SendMessageApi.Builder?) -> Boolean): SendMessageApi.Builder? { - val current = builderOrNull - return if (createNew(builderOrNull)) { - newBuilder() - } else { - current - } - } - fun end(): List { - archiveCurrent() return apiStacks } } diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResultApiElement.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResultApiElement.kt index 6629769..eb87b89 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResultApiElement.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramMessageResultApiElement.kt @@ -73,7 +73,6 @@ internal object TelegramMessageResultApiElementSendingResolver : SendingMessageR context: SendingMessageResolverContext ) { if (element is TelegramMessageResultApiElement) { - context.archiveCurrent() when (element) { is MessageIdResult -> { context.addToStackMsgId { element.api } diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextParseMode.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextParseMode.kt new file mode 100644 index 0000000..f06e3c6 --- /dev/null +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextParseMode.kt @@ -0,0 +1,38 @@ +/* + * 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.message + +import kotlinx.serialization.Serializable +import love.forte.simbot.message.PlainText +import love.forte.simbot.telegram.api.message.SendMessageApi +import love.forte.simbot.telegram.type.FormattingOption + +/** + * A [PlainText]'s [`parse_mode`](https://core.telegram.org/bots/api#formatting-options) + * (see also [FormattingOption]). + * + * @see SendMessageApi.Body.parseMode + * @see FormattingOption + */ +@Serializable +public data class TelegramTextParseMode(val parseMode: String) : TelegramMessageElement { + public constructor(formattingOption: FormattingOption) : this(formattingOption.value) +} + +// The Message.entities + diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/ReceivingMessageResolvers.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/ReceivingMessageResolvers.kt index 609678c..9e5aaf1 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/ReceivingMessageResolvers.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/ReceivingMessageResolvers.kt @@ -17,9 +17,13 @@ package love.forte.simbot.component.telegram.core.message.internal +import love.forte.simbot.common.id.StringID.Companion.ID import love.forte.simbot.component.telegram.core.message.StdlibMessage +import love.forte.simbot.component.telegram.core.message.TelegramMessageEntity import love.forte.simbot.message.Messages import love.forte.simbot.message.MessagesBuilder +import love.forte.simbot.telegram.type.MessageEntity +import love.forte.simbot.telegram.type.MessageEntityType /** * @@ -33,9 +37,72 @@ internal fun StdlibMessage.toMessages(): Messages { // TODO replyToStory: Story ..? // TODO hasProtectedContent: Boolean ..? - text?.also { builder.add(it) } + text?.also { text -> + if (entities.isNullOrEmpty()) { + builder.add(text) + return@also + } - // TODO entities ..? + var lastOffset = 0 + + entities?.forEach { entity -> + if (lastOffset != entity.offset) { + // 上一次与当前中间夹着字符串 + builder.add(text.substring(lastOffset, entity.offset)) + } + lastOffset = entity.offset + entity.length + val messageEntity = when (entity.type.uppercase()) { + MessageEntityType.TEXT_LINK.name -> { + TelegramMessageEntity.TextLink( + text = text.substring(entity), + url = entity.url, + entity + ) + } + + MessageEntityType.TEXT_MENTION.name -> { + TelegramMessageEntity.TextMention( + text = text.substring(entity), + user = entity.user, + entity + ) + } + + MessageEntityType.PRE.name -> { + TelegramMessageEntity.Pre( + text = text.substring(entity), + language = entity.language, + entity + ) + } + + MessageEntityType.CUSTOM_EMOJI.name -> { + TelegramMessageEntity.CustomEmoji( + text = text.substring(entity), + customEmojiId = entity.customEmojiId?.ID, + entity + ) + } + + // simple + else -> { + TelegramMessageEntity.Simple( + text = text.substring(entity), + type = entity.type, + entity + ) + } + } + builder.add(messageEntity) + } + + if (lastOffset != text.length) { + // 字符串有残留 + builder.add(text.substring(lastOffset, text.length)) + } + + + } // TODO linkPreviewOptions: LinkPreviewOptions // TODO animation: Animation @@ -60,3 +127,7 @@ internal fun StdlibMessage.toMessages(): Messages { return builder.build() } + + +private fun String.substring(entity: MessageEntity): String = + substring(entity.offset, entity.offset + entity.length) diff --git a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/SendingMessageStandardResolvers.kt b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/SendingMessageStandardResolvers.kt index 37ab565..87f783f 100644 --- a/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/SendingMessageStandardResolvers.kt +++ b/simbot-component-telegram-core/src/commonMain/kotlin/love/forte/simbot/component/telegram/core/message/internal/SendingMessageStandardResolvers.kt @@ -17,21 +17,93 @@ package love.forte.simbot.component.telegram.core.message.internal +import love.forte.simbot.common.id.literal import love.forte.simbot.component.telegram.core.message.SendingMessageResolver import love.forte.simbot.component.telegram.core.message.SendingMessageResolverContext +import love.forte.simbot.component.telegram.core.message.TelegramMessageEntity +import love.forte.simbot.component.telegram.core.message.TelegramTextParseMode import love.forte.simbot.message.Message import love.forte.simbot.message.PlainText +import love.forte.simbot.telegram.type.MessageEntity -internal object PlainTextResolver : SendingMessageResolver { +internal object TextSendingResolver : SendingMessageResolver { override suspend fun resolve( index: Int, element: Message.Element, source: Message, context: SendingMessageResolverContext ) { - if (element is PlainText) { - context.builder.text(element.text) + with(context.builder) { + when (element) { + is TelegramTextParseMode -> { + parseMode = element.parseMode + } + + is PlainText -> { + val textValue = element.text + if (element is TelegramMessageEntity) { + val entityType = element.type + when (element) { + is TelegramMessageEntity.Simple -> { + addEntity( + MessageEntity( + type = entityType, + offset = textOffset, + length = textValue.length + ) + ) + } + + is TelegramMessageEntity.TextLink -> { + addEntity( + MessageEntity( + type = entityType, + offset = textOffset, + length = textValue.length, + url = element.url + ) + ) + } + + is TelegramMessageEntity.TextMention -> { + addEntity( + MessageEntity( + type = entityType, + offset = textOffset, + length = textValue.length, + user = element.user + ) + ) + } + + is TelegramMessageEntity.Pre -> { + addEntity( + MessageEntity( + type = entityType, + offset = textOffset, + length = textValue.length, + language = element.language + ) + ) + } + + is TelegramMessageEntity.CustomEmoji -> { + addEntity( + MessageEntity( + type = entityType, + offset = textOffset, + length = textValue.length, + customEmojiId = element.customEmojiId?.literal + ) + ) + } + } + } + appendText(textValue) + } + + } } - // TODO TelegramText? (can with parse mode option) } } + diff --git a/simbot-component-telegram-core/src/commonTest/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextEntitiesTests.kt b/simbot-component-telegram-core/src/commonTest/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextEntitiesTests.kt new file mode 100644 index 0000000..b9900ee --- /dev/null +++ b/simbot-component-telegram-core/src/commonTest/kotlin/love/forte/simbot/component/telegram/core/message/TelegramTextEntitiesTests.kt @@ -0,0 +1,185 @@ +package love.forte.simbot.component.telegram.core.message + +import kotlinx.coroutines.test.runTest +import love.forte.simbot.common.id.StringID.Companion.ID +import love.forte.simbot.common.id.literal +import love.forte.simbot.component.telegram.core.message.internal.toMessages +import love.forte.simbot.message.Text +import love.forte.simbot.message.toMessages +import love.forte.simbot.telegram.api.message.SendMessageApi +import love.forte.simbot.telegram.type.* +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertIs +import kotlin.test.assertNotNull + + +/** + * + * @author ForteScarlet + */ +class TelegramTextEntitiesTests { + + @Test + fun textEntitiesSendResolveTest() = runTest { + val textLink = TelegramMessageEntity.createTextLink("GitHub", "https://github.com/") + val textMention = TelegramMessageEntity.createTextMention("@forte", User(id = 100, firstName = "forte")) + val preCode = TelegramMessageEntity.createPre("CODE", "JAVA") + val customEmoji = TelegramMessageEntity.createCustomEmoji("EMOJI", "EMOJI".ID) + val simple = TelegramMessageEntity.create("1=1", MessageEntityType.CODE) + + assertIs(textLink) + assertIs(textMention) + assertIs(preCode) + assertIs(customEmoji) + assertIs(simple) + + val resolved = listOf( + textLink, + textMention, + Text { "Hello" }, + preCode, + customEmoji, + simple + ).toMessages().resolve { + SendMessageApi.builder().apply { + chatId = ChatId(10000) + } + } + + val body = resolved.first()().body + assertIs(body) + val text = body.text + assertEquals("GitHub@forteHelloCODEEMOJI1=1", text) + + val entities = body.entities + assertNotNull(entities) + // textLink + with(entities.first { it.type == textLink.type }) { + assertEquals(textLink.type, type) + assertEquals(textLink.url, url) + assertEquals( + textLink.text, + text.substring(this) + ) + } + // textMention + with(entities.first { it.type == textMention.type }) { + assertEquals(textMention.type, type) + assertEquals(textMention.user?.id, user?.id) + assertEquals( + textMention.text, + text.substring(this) + ) + } + + // preCode + with(entities.first { it.type == preCode.type }) { + assertEquals(preCode.type, type) + assertEquals(preCode.language, language) + assertEquals( + preCode.text, + text.substring(this) + ) + } + + // customEmoji + with(entities.first { it.type == customEmoji.type }) { + assertEquals(customEmoji.type, type) + assertEquals(customEmoji.customEmojiId?.literal, customEmojiId) + assertEquals( + customEmoji.text, + text.substring(this) + ) + } + + // simple + with(entities.first { it.type == simple.type }) { + assertEquals(simple.type, type) + assertEquals( + simple.text, + text.substring(this) + ) + } + + } + + @Test + fun receivedMessageToMessagesTest() = runTest { + val textLink = TelegramMessageEntity.createTextLink("GitHub", "https://github.com/") + val textMention = TelegramMessageEntity.createTextMention("@forte", User(id = 100, firstName = "forte")) + val preCode = TelegramMessageEntity.createPre("CODE", "JAVA") + val customEmoji = TelegramMessageEntity.createCustomEmoji("EMOJI", "EMOJI".ID) + val simple = TelegramMessageEntity.create("1=1", MessageEntityType.CODE) + + val resolved = listOf( + textLink, + textMention, + Text { "Hello" }, + preCode, + customEmoji, + simple + ).toMessages().resolve { + SendMessageApi.builder().apply { + chatId = ChatId(10000) + } + } + + val body = resolved.first()().body + assertIs(body) + val bodyText = body.text + assertEquals("GitHub@forteHelloCODEEMOJI1=1", bodyText) + + val messages = Message( + messageId = 10000, + chat = Chat( + id = 10000L, + title = "Test Chat", + type = "channel" + ), + text = bodyText, + entities = body.entities?.toList(), + date = 1234567890, + ).toMessages().toList() + + // textLink, + // textMention, + // Text { "Hello" }, + // preCode, + // customEmoji, + // simple + + assertEquals(6, messages.size) + + with(messages[0]) { + assertIs(this) + assertEquals(textLink.text, text) + } + with(messages[1]) { + assertIs(this) + assertEquals(textMention.text, text) + } + with(messages[2]) { + assertIs(this) + assertEquals("Hello", text) + } + with(messages[3]) { + assertIs(this) + assertEquals(preCode.text, text) + } + with(messages[4]) { + assertIs(this) + assertEquals(customEmoji.text, text) + } + with(messages[5]) { + assertIs(this) + assertEquals(simple.text, text) + } + + + } + +} + +private fun String.substring(entity: MessageEntity): String = + substring(entity.offset, entity.offset + entity.length) diff --git a/simbot-component-telegram-type/src/commonMain/kotlin/love/forte/simbot/telegram/type/Message.kt b/simbot-component-telegram-type/src/commonMain/kotlin/love/forte/simbot/telegram/type/Message.kt index 57f7ba4..7198a79 100644 --- a/simbot-component-telegram-type/src/commonMain/kotlin/love/forte/simbot/telegram/type/Message.kt +++ b/simbot-component-telegram-type/src/commonMain/kotlin/love/forte/simbot/telegram/type/Message.kt @@ -705,13 +705,25 @@ public data class MessageAutoDeleteTimerChanged( public data class MessageEntity( /** * Type of the entity. - * Currently, can be “mention” (@username), “hashtag” (#hashtag), “cashtag” ($USD), - * “bot_command” (/start@jobs_bot), “url” (https://telegram.org), “email” - * (do-not-reply@telegram.org), “phone_number” (+1-212-555-0123), “bold” (bold text), “italic” - * (italic text), “underline” (underlined text), “strikethrough” (strikethrough text), “spoiler” - * (spoiler message), “blockquote” (block quotation), “code” (monowidth string), “pre” (monowidth - * block), “text_link” (for clickable text URLs), “text_mention” (for users without usernames), - * “custom_emoji” (for inline custom emoji stickers) + * Currently, can be + * - “mention” (@username), + * - “hashtag” (#hashtag), + * - “cashtag” ($USD), + * - “bot_command” (/start@jobs_bot), + * - “url” (https://telegram.org), + * - “email” (do-not-reply@telegram.org), + * - “phone_number” (+1-212-555-0123), + * - “bold” (bold text), + * - “italic” (italic text), + * - “underline” (underlined text), + * - “strikethrough” (strikethrough text), + * - “spoiler” (spoiler message), + * - “blockquote” (block quotation), + * - “code” (monowidth string), + * - “pre” (monowidth block), + * - “text_link” (for clickable text URLs), + * - “text_mention” (for users without usernames), + * - “custom_emoji” (for inline custom emoji stickers) * * type: `String` */ @@ -760,6 +772,38 @@ public data class MessageEntity( public val customEmojiId: String? = null, ) +/** + * The type (uppercase) of [MessageEntity.type]. + */ +@Serializable +public enum class MessageEntityType { + MENTION, + HASHTAG, + CASHTAG, + BOT_COMMAND, + URL, + EMAIL, + PHONE_NUMBER, + BOLD, + ITALIC, + UNDERLINE, + STRIKETHROUGH, + SPOILER, + BLOCKQUOTE, + CODE, + PRE, + TEXT_LINK, + TEXT_MENTION, + CUSTOM_EMOJI, + ; + + /** + * The lowercase value of name. + */ + public val typeValue: String + get() = name.lowercase() +} + /** * [MessageId](https://core.telegram.org/bots/api#messageid) * From 6d904c60dd8de1b3dd42a20b6b6c6c3e7828f1e2 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Fri, 12 Apr 2024 01:36:31 +0800 Subject: [PATCH 2/3] test: try to fix test timeout --- .../stdlib/event/BotEventSubscribeTests.kt | 79 +++++++++++++------ 1 file changed, 53 insertions(+), 26 deletions(-) diff --git a/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt b/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt index 9186814..0e24574 100644 --- a/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt +++ b/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt @@ -1,8 +1,10 @@ package love.forte.simbot.telegram.stdlib.event import io.ktor.client.engine.mock.* +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext import love.forte.simbot.telegram.stdlib.* import love.forte.simbot.telegram.stdlib.bot.* import love.forte.simbot.telegram.type.MessageOriginChannel @@ -20,6 +22,7 @@ import kotlin.test.assertNotNull class BotEventSubscribeTests { private suspend fun botAndStart(): Bot { return BotFactory.create("TOKEN") { + coroutineContext = Dispatchers.Default apiClientEngine = MockEngine { respondOk() } @@ -38,10 +41,13 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(MESSAGE_WITH_TEXT) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(MESSAGE_WITH_TEXT) + + onMessaged.join() + onMessaged2.join() + } - onMessaged.join() - onMessaged2.join() } @Test @@ -54,10 +60,11 @@ class BotEventSubscribeTests { assertIs(message.forwardOrigin) onMessaged.complete() } + withContext(Dispatchers.Default) { + bot.pushRawUpdate(FORWARDED_MESSAGE) - bot.pushRawUpdate(FORWARDED_MESSAGE) - - onMessaged.join() + onMessaged.join() + } } @Test @@ -71,9 +78,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(FORWARDED_CHANNEL_MESSAGE) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(FORWARDED_CHANNEL_MESSAGE) - onMessaged.join() + onMessaged.join() + } } @Test @@ -86,9 +95,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(MESSAGE_WITH_A_REPLY) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(MESSAGE_WITH_A_REPLY) - onMessaged.join() + onMessaged.join() + } } @Test @@ -104,9 +115,11 @@ class BotEventSubscribeTests { onMessaged.completeExceptionally(IllegalStateException("NOT onMessage")) } - bot.pushRawUpdate(EDITED_MESSAGE) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(EDITED_MESSAGE) - onMessaged.join() + onMessaged.join() + } } @Test @@ -121,9 +134,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(MESSAGE_WITH_ENTITIES) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(MESSAGE_WITH_ENTITIES) - onMessaged.join() + onMessaged.join() + } } @Test @@ -136,9 +151,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(MESSAGE_WITH_AUDIO) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(MESSAGE_WITH_AUDIO) - onMessaged.join() + onMessaged.join() + } } @Test @@ -151,9 +168,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(VOICE_MESSAGE) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(VOICE_MESSAGE) - onMessaged.join() + onMessaged.join() + } } @Test @@ -166,9 +185,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(MESSAGE_WITH_A_DOCUMENT) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(MESSAGE_WITH_A_DOCUMENT) - onMessaged.join() + onMessaged.join() + } } @Test @@ -180,9 +201,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(INLINE_QUERY) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(INLINE_QUERY) - onMessaged.join() + onMessaged.join() + } } @Test @@ -194,9 +217,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(CHOSEN_INLINE_QUERY) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(CHOSEN_INLINE_QUERY) - onMessaged.join() + onMessaged.join() + } } @Test @@ -208,9 +233,11 @@ class BotEventSubscribeTests { onMessaged.complete() } - bot.pushRawUpdate(CALLBACK_QUERY) + withContext(Dispatchers.Default) { + bot.pushRawUpdate(CALLBACK_QUERY) - onMessaged.join() + onMessaged.join() + } } } From 39f8490ec9ee06fd56b7f124ab6cd7c835d86d70 Mon Sep 17 00:00:00 2001 From: ForteScarlet Date: Fri, 12 Apr 2024 01:57:56 +0800 Subject: [PATCH 3/3] test: try to fix test timeout --- .../stdlib/event/BotEventSubscribeTests.kt | 80 ++++++------------- 1 file changed, 26 insertions(+), 54 deletions(-) diff --git a/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt b/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt index 0e24574..aa90b82 100644 --- a/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt +++ b/simbot-component-telegram-stdlib/src/commonTest/kotlin/love/forte/simbot/telegram/stdlib/event/BotEventSubscribeTests.kt @@ -1,10 +1,8 @@ package love.forte.simbot.telegram.stdlib.event import io.ktor.client.engine.mock.* -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.test.runTest -import kotlinx.coroutines.withContext import love.forte.simbot.telegram.stdlib.* import love.forte.simbot.telegram.stdlib.bot.* import love.forte.simbot.telegram.type.MessageOriginChannel @@ -22,7 +20,7 @@ import kotlin.test.assertNotNull class BotEventSubscribeTests { private suspend fun botAndStart(): Bot { return BotFactory.create("TOKEN") { - coroutineContext = Dispatchers.Default + // coroutineContext = Dispatchers.Default apiClientEngine = MockEngine { respondOk() } @@ -35,19 +33,15 @@ class BotEventSubscribeTests { fun messageWithTextTest() = runTest { val bot = botAndStart() val onMessaged = Job() - val onMessaged2 = Job() bot.onMessage { _, _ -> onMessaged.complete() + bot.cancel() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(MESSAGE_WITH_TEXT) - - onMessaged.join() - onMessaged2.join() - } + bot.pushRawUpdate(MESSAGE_WITH_TEXT) + onMessaged.join() } @Test @@ -60,11 +54,9 @@ class BotEventSubscribeTests { assertIs(message.forwardOrigin) onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(FORWARDED_MESSAGE) + bot.pushRawUpdate(FORWARDED_MESSAGE) - onMessaged.join() - } + onMessaged.join() } @Test @@ -78,11 +70,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(FORWARDED_CHANNEL_MESSAGE) + bot.pushRawUpdate(FORWARDED_CHANNEL_MESSAGE) - onMessaged.join() - } + onMessaged.join() } @Test @@ -95,11 +85,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(MESSAGE_WITH_A_REPLY) + bot.pushRawUpdate(MESSAGE_WITH_A_REPLY) - onMessaged.join() - } + onMessaged.join() } @Test @@ -115,11 +103,9 @@ class BotEventSubscribeTests { onMessaged.completeExceptionally(IllegalStateException("NOT onMessage")) } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(EDITED_MESSAGE) + bot.pushRawUpdate(EDITED_MESSAGE) - onMessaged.join() - } + onMessaged.join() } @Test @@ -134,11 +120,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(MESSAGE_WITH_ENTITIES) + bot.pushRawUpdate(MESSAGE_WITH_ENTITIES) - onMessaged.join() - } + onMessaged.join() } @Test @@ -151,11 +135,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(MESSAGE_WITH_AUDIO) + bot.pushRawUpdate(MESSAGE_WITH_AUDIO) - onMessaged.join() - } + onMessaged.join() } @Test @@ -168,11 +150,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(VOICE_MESSAGE) + bot.pushRawUpdate(VOICE_MESSAGE) - onMessaged.join() - } + onMessaged.join() } @Test @@ -185,11 +165,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(MESSAGE_WITH_A_DOCUMENT) + bot.pushRawUpdate(MESSAGE_WITH_A_DOCUMENT) - onMessaged.join() - } + onMessaged.join() } @Test @@ -201,11 +179,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(INLINE_QUERY) + bot.pushRawUpdate(INLINE_QUERY) - onMessaged.join() - } + onMessaged.join() } @Test @@ -217,11 +193,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(CHOSEN_INLINE_QUERY) + bot.pushRawUpdate(CHOSEN_INLINE_QUERY) - onMessaged.join() - } + onMessaged.join() } @Test @@ -233,11 +207,9 @@ class BotEventSubscribeTests { onMessaged.complete() } - withContext(Dispatchers.Default) { - bot.pushRawUpdate(CALLBACK_QUERY) + bot.pushRawUpdate(CALLBACK_QUERY) - onMessaged.join() - } + onMessaged.join() } }