From 71b338384e2dce2fc9712b63d7eb2dd6ca6460de Mon Sep 17 00:00:00 2001 From: LaoLittle <93422095+LaoLittle@users.noreply.github.com> Date: Mon, 31 Jan 2022 16:34:35 +0800 Subject: [PATCH] feat: enhancement (#13) - add commenting drift bottle feature - add a composite command for querying and deleting items in the sea - add an option for disabling certain message types - add a reply when commands invocation is throttled Signed-off-by: Samarium <28302241+Samarium150@users.noreply.github.com> Co-authored-by: Samarium <28302241+Samarium150@users.noreply.github.com> --- build.gradle.kts | 4 +- .../driftbottle/MiraiConsoleDriftBottle.kt | 37 +++++--- .../plugin/driftbottle/command/Comment.kt | 75 +++++++++++++++ .../plugin/driftbottle/command/JumpInto.kt | 19 ++-- .../plugin/driftbottle/command/Pickup.kt | 55 ++++++----- .../driftbottle/command/SeaOperation.kt | 78 +++++++++++++++ .../plugin/driftbottle/command/ThrowAway.kt | 43 +++++---- .../driftbottle/config/AdvancedConfig.kt | 63 +++++++++++- .../driftbottle/config/CommandConfig.kt | 15 ++- .../driftbottle/config/GeneralConfig.kt | 7 +- .../plugin/driftbottle/config/ReplyConfig.kt | 15 ++- .../plugin/driftbottle/data/CommentData.kt | 66 +++++++++++++ .../mirai/plugin/driftbottle/data/Item.kt | 90 ++++++++++-------- .../plugin/driftbottle/util/ContentCensor.kt | 8 +- .../util/CustomForwardMsgDisplay.kt | 40 ++++++++ .../mirai/plugin/driftbottle/util/General.kt | 95 ++++++++++++++++++- .../mirai/plugin/driftbottle/util/Throttle.kt | 16 ++++ 17 files changed, 598 insertions(+), 128 deletions(-) create mode 100644 src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Comment.kt create mode 100644 src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/SeaOperation.kt create mode 100644 src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/CommentData.kt create mode 100644 src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/CustomForwardMsgDisplay.kt diff --git a/build.gradle.kts b/build.gradle.kts index bf66cff..beae4ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,11 +10,11 @@ plugins { } group = "io.github.samarium150" -version = "1.5.0" +version = "1.6.0" repositories { mavenLocal() - maven("https://maven.aliyun.com/repository/public") + maven("https://maven.aliyun.com/repository/central") mavenCentral() } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/MiraiConsoleDriftBottle.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/MiraiConsoleDriftBottle.kt index 177d744..1a014af 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/MiraiConsoleDriftBottle.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/MiraiConsoleDriftBottle.kt @@ -16,15 +16,12 @@ */ package io.github.samarium150.mirai.plugin.driftbottle -import io.github.samarium150.mirai.plugin.driftbottle.command.JumpInto -import io.github.samarium150.mirai.plugin.driftbottle.command.Pickup -import io.github.samarium150.mirai.plugin.driftbottle.command.ThrowAway -import io.github.samarium150.mirai.plugin.driftbottle.config.CommandConfig -import io.github.samarium150.mirai.plugin.driftbottle.config.ContentCensorConfig -import io.github.samarium150.mirai.plugin.driftbottle.config.GeneralConfig -import io.github.samarium150.mirai.plugin.driftbottle.config.ReplyConfig +import io.github.samarium150.mirai.plugin.driftbottle.command.* +import io.github.samarium150.mirai.plugin.driftbottle.config.* +import io.github.samarium150.mirai.plugin.driftbottle.data.CommentData import io.github.samarium150.mirai.plugin.driftbottle.data.ContentCensorToken import io.github.samarium150.mirai.plugin.driftbottle.data.Sea +import io.github.samarium150.mirai.plugin.driftbottle.util.alsoSave import io.ktor.client.* import io.ktor.client.features.json.* import io.ktor.client.features.json.serializer.* @@ -37,7 +34,7 @@ object MiraiConsoleDriftBottle : KotlinPlugin( JvmPluginDescription( id = "io.github.samarium150.mirai.plugin.mirai-console-drift-bottle", name = "Drift Bottle", - version = "1.5.0", + version = "1.6.0", ) { author("Samarium150") info("简单的漂流瓶插件") @@ -46,19 +43,33 @@ object MiraiConsoleDriftBottle : KotlinPlugin( lateinit var client: HttpClient - override fun onEnable() { - // 重载数据 - GeneralConfig.reload() - ReplyConfig.reload() - CommandConfig.reload() + private fun init() { + // 重载只读配置 + AdvancedConfig.alsoSave() + GeneralConfig.alsoSave() + ReplyConfig.alsoSave() + CommandConfig.alsoSave() + + // 重载配置 ContentCensorConfig.reload() + + // 重载数据 ContentCensorToken.reload() + CommentData.reload() Sea.reload() // 注册命令 JumpInto.register() Pickup.register() ThrowAway.register() + SeaOperation.register() + if (GeneralConfig.incrementalBottle) + Comment.register() + } + + override fun onEnable() { + // 初始化插件 + init() // 初始化 HTTP 客户端 if (GeneralConfig.enableContentCensor) diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Comment.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Comment.kt new file mode 100644 index 0000000..e8a1a1a --- /dev/null +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Comment.kt @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2020-2021 Samarium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ +package io.github.samarium150.mirai.plugin.driftbottle.command + +import io.github.samarium150.mirai.plugin.driftbottle.MiraiConsoleDriftBottle +import io.github.samarium150.mirai.plugin.driftbottle.config.CommandConfig +import io.github.samarium150.mirai.plugin.driftbottle.data.CommentData +import io.github.samarium150.mirai.plugin.driftbottle.util.indexOfBottle +import io.github.samarium150.mirai.plugin.driftbottle.util.isNotOutOfRange +import io.github.samarium150.mirai.plugin.driftbottle.util.randomDelay +import net.mamoe.mirai.console.command.CommandSenderOnMessage +import net.mamoe.mirai.console.command.SimpleCommand +import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.contact.nameCardOrNick +import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.SingleMessage + +/** + * @author LaoLittle + */ +object Comment : SimpleCommand( + MiraiConsoleDriftBottle, + primaryName = "comment", + secondaryNames = CommandConfig.comment, + description = "评论漂流瓶" +) { + private val comments by CommentData.Companion::comments + + @ExperimentalCommandDescriptors + @ConsoleExperimentalApi + override val prefixOptional: Boolean = true + + override val usage: String + get() = "评论 内容 漂流瓶序号" + + @Suppress("unused") + @Handler + suspend fun CommandSenderOnMessage<*>.handle( + comment: SingleMessage, + index: Int? = indexOfBottle[fromEvent.subject.id]?.takeIf { it.isNotEmpty() }?.peek()?.plus(1) + + ) { + if (comment !is PlainText) { + sendMessage("评论只能包含纯文本!") + return + } + val commentStr = comment.content + val realIndex = index?.minus(1) + if (isNotOutOfRange(realIndex)) { + val nick = fromEvent.sender.nameCardOrNick + val id = fromEvent.sender.id + comments[realIndex]?.add(CommentData(id, nick, commentStr)) ?: comments.put( + realIndex, + mutableListOf(CommentData(id, nick, commentStr)) + ) + randomDelay() + sendMessage("已评论漂流瓶 $index") // 或许可由用户自行配置 + } + } +} diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/JumpInto.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/JumpInto.kt index 3bac469..9e33409 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/JumpInto.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/JumpInto.kt @@ -48,10 +48,13 @@ object JumpInto : SimpleCommand( @Handler suspend fun CommandSender.handle() { val sender = user - if (sender == null) randomDelay().also { + if (sender == null) sendMessage(ReplyConfig.jumpInto.replace("%num", Sea.contents.size.toString())) - } else { - if (!lock(sender.id)) return + else { + if (!lock(sender.id)) { + sendMessage(ReplyConfig.inCooldown) + return + } val subject = subject val owner = Owner( sender.id, @@ -64,12 +67,12 @@ object JumpInto : SimpleCommand( ) else null val body = Item(Item.Type.BODY, owner, source) Sea.contents.add(body) - randomDelay().also { - sendMessage(ReplyConfig.jumpInto.replace("%num", Sea.contents.size.toString())).also { - delay(GeneralConfig.perUse * 1000L) - unlock(sender.id) - } + runCatching { + randomDelay() + sendMessage(ReplyConfig.jumpInto.replace("%num", (Sea.contents.size - 1).toString())) + delay(GeneralConfig.perUse * 1000L) } + unlock(sender.id) } } } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Pickup.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Pickup.kt index df672c9..370ba27 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Pickup.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/Pickup.kt @@ -22,15 +22,13 @@ import io.github.samarium150.mirai.plugin.driftbottle.config.GeneralConfig import io.github.samarium150.mirai.plugin.driftbottle.config.ReplyConfig import io.github.samarium150.mirai.plugin.driftbottle.data.Item import io.github.samarium150.mirai.plugin.driftbottle.data.Sea -import io.github.samarium150.mirai.plugin.driftbottle.util.disableAt -import io.github.samarium150.mirai.plugin.driftbottle.util.lock -import io.github.samarium150.mirai.plugin.driftbottle.util.randomDelay -import io.github.samarium150.mirai.plugin.driftbottle.util.unlock +import io.github.samarium150.mirai.plugin.driftbottle.util.* import kotlinx.coroutines.delay import net.mamoe.mirai.console.command.CommandSenderOnMessage import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.message.data.MessageChain import java.util.* object Pickup : SimpleCommand( @@ -45,28 +43,41 @@ object Pickup : SimpleCommand( @Suppress("unused") @Handler - suspend fun CommandSenderOnMessage<*>.handle() { - val sender = fromEvent.sender - val subject = fromEvent.subject - if (!lock(sender.id)) return - if (Sea.contents.size == 0) { - randomDelay().also { - sendMessage(ReplyConfig.noItem) + suspend fun CommandSenderOnMessage<*>.handle(index: Int = Random().nextInt(Sea.contents.size) + 1) { + val realIndex = index - 1 + if (isNotOutOfRange(realIndex)) { + val sender = fromEvent.sender + val subject = fromEvent.subject + if (!lock(sender.id)) { + sendMessage(ReplyConfig.inCooldown) + return + } + if (Sea.contents.size == 0) { + randomDelay() unlock(sender.id) + sendMessage(ReplyConfig.noItem) + return } - return - } - val index = Random().nextInt(Sea.contents.size) - val item = Sea.contents[index] - if ((item.type == Item.Type.BOTTLE && !GeneralConfig.incrementalBottle) - || (item.type == Item.Type.BODY && !GeneralConfig.incrementalBody) - ) - Sea.contents.removeAt(index) - randomDelay().also { - sendMessage(disableAt(item.toMessageChain(subject), subject)).also { + val item = Sea.contents[realIndex] + if ((item.type == Item.Type.BOTTLE && !GeneralConfig.incrementalBottle) + || (item.type == Item.Type.BODY && !GeneralConfig.incrementalBody) + ) { + Sea.contents.removeAt(realIndex) + rearrangeComments(realIndex) + } else { + indexOfBottle[subject.id]?.remove(realIndex) + indexOfBottle[subject.id]?.push(realIndex) ?: indexOfBottle.put( + subject.id, + Stack().put(realIndex) + ) + } + runCatching { + randomDelay() + val message = item.toMessage(subject, realIndex) + sendMessage(if (message is MessageChain) disableAt(message, subject) else message) delay(GeneralConfig.perUse * 1000L) - unlock(sender.id) } + unlock(sender.id) } } } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/SeaOperation.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/SeaOperation.kt new file mode 100644 index 0000000..6cc42e2 --- /dev/null +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/SeaOperation.kt @@ -0,0 +1,78 @@ +/** + * Copyright (c) 2020-2021 Samarium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ +package io.github.samarium150.mirai.plugin.driftbottle.command + +import io.github.samarium150.mirai.plugin.driftbottle.MiraiConsoleDriftBottle +import io.github.samarium150.mirai.plugin.driftbottle.config.CommandConfig +import io.github.samarium150.mirai.plugin.driftbottle.data.Sea +import io.github.samarium150.mirai.plugin.driftbottle.util.* +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.command.CompositeCommand + +/** + * @author LaoLittle + */ +object SeaOperation : CompositeCommand( + MiraiConsoleDriftBottle, + primaryName = "sea", + secondaryNames = CommandConfig.seaOperation, + description = "漂流瓶操作复合指令" +) { + @Suppress("unused") + @SubCommand("del", "rm") + suspend fun CommandSender.remove( + index: Int? = subject?.let { sub -> + indexOfBottle[sub.id]?.takeIf { it.isNotEmpty() }?.pop()?.plus(1) + } + ) { + val realIndex = index?.minus(1) + if (isNotOutOfRange(realIndex)) { + val result = runCatching { + Sea.contents.removeAt(realIndex) + rearrangeComments(realIndex) + }.onFailure { e -> + if (e !is IndexOutOfBoundsException) MiraiConsoleDriftBottle.logger.error(e) + } + randomDelay() + sendMessage(if (result.isSuccess) "已删除漂流瓶$index" else "删除漂流瓶$index 失败") + } + } + + @Suppress("unused") + @SubCommand("query", "get") + suspend fun CommandSender.query( + index: Int? = subject?.let { sub -> + indexOfBottle[sub.id]?.takeIf { it.isNotEmpty() }?.peek()?.plus(1) + } + ) { + val realIndex = index?.minus(1) + if (isNotOutOfRange(realIndex)) { + val result = runCatching { + val item = Sea.contents[realIndex] + """ + 漂流瓶序号: $index + ${item.source?.let { "漂流瓶发送群: ${it.name}(${it.id})" } ?: "此漂流瓶是私聊发送"} + 漂流瓶发送人: ${item.owner.name}(${item.owner.id}) + 漂流瓶发送时间: ${item.timestamp.timestampToString()} + """.trimIndent() + }.onFailure { e -> + if (e !is IndexOutOfBoundsException) MiraiConsoleDriftBottle.logger.error(e) + } + sendMessage(result.getOrNull() ?: "无法找到漂流瓶$index") + } + } +} diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/ThrowAway.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/ThrowAway.kt index 056023b..d8a16d5 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/ThrowAway.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/command/ThrowAway.kt @@ -52,24 +52,31 @@ object ThrowAway : SimpleCommand( suspend fun CommandSenderOnMessage<*>.handle(vararg messages: Message = arrayOf()) { val sender = fromEvent.sender val subject = fromEvent.subject - if (!lock(sender.id)) return + if (!lock(sender.id)) { + sendMessage(ReplyConfig.inCooldown) + return + } val chain = if (messages.isNotEmpty()) messageChainOf(*messages) else { - randomDelay().also { - sendMessage(ReplyConfig.waitForNextMessage) - } + randomDelay() + sendMessage(ReplyConfig.waitForNextMessage) runCatching { fromEvent.nextMessage(30_000) }.onFailure { - sendMessage(ReplyConfig.timeoutMessage) - }.getOrNull() ?: run { unlock(sender.id) - return@handle + sendMessage(ReplyConfig.timeout) + }.getOrNull() ?: return + } + forbidMessageKeys.forEach { + if (chain.contains(it)) { + unlock(sender.id) + sendMessage(ReplyConfig.bannedMessageType) + return } } if (GeneralConfig.enableContentCensor) runCatching { if (!ContentCensor.determine(chain)) { - sendMessage(ReplyConfig.invalidMessage) + sendMessage(ReplyConfig.invalid) return } }.onFailure { @@ -94,18 +101,16 @@ object ThrowAway : SimpleCommand( Sea.contents.add(bottle) val parts = ReplyConfig.throwAway.split("%content") runCatching { - randomDelay().also { - if (parts.size == 1) sendMessage(parts[0]) - else - sendMessage(buildMessageChain { - +PlainText(parts[0]) - +disableAt(chain, subject) - +PlainText(parts[1]) - }) - } - }.also { + randomDelay() + if (parts.size == 1) sendMessage(parts[0]) + else + sendMessage(buildMessageChain { + +PlainText(parts[0]) + +disableAt(chain, subject) + +PlainText(parts[1]) + }) delay(GeneralConfig.perUse * 1000L) - unlock(sender.id) } + unlock(sender.id) } } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/AdvancedConfig.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/AdvancedConfig.kt index 09f499e..3c0a18f 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/AdvancedConfig.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/AdvancedConfig.kt @@ -1,11 +1,70 @@ +/** + * Copyright (c) 2020-2021 Samarium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ package io.github.samarium150.mirai.plugin.driftbottle.config -import net.mamoe.mirai.console.data.AutoSavePluginConfig +import kotlinx.serialization.Serializable +import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault +import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value +import net.mamoe.mirai.console.util.ConsoleExperimentalApi +import net.mamoe.mirai.message.data.MessageContent +import net.mamoe.mirai.message.data.MessageKey -object AdvancedConfig : AutoSavePluginConfig("Advanced") { +object AdvancedConfig : ReadOnlyPluginConfig("Advanced") { @ValueDescription("At显示为纯文本") val disableDirectAt by value(false) + + @ConsoleExperimentalApi + @ValueDescription( + """ + 漂流瓶消息类型控制 + 可在此配置以下类型: + Image: 图片, LightApp: 小程序, Audio: 语音, FlashImage: 闪照 + MarketFace: 商城表情, Dice: 骰子, ForwardMessage: 合并转发消息 + FileMessage: 文件消息 (貌似漂流瓶无法存放文件,但还是先放着) + """ + ) + val saveMessageTypes by value(MessageType.values().associateWith { true }).withDefault { true } + + @Serializable + enum class MessageType { + Image, + LightApp, + Audio, + FlashImage, + MarketFace, + Dice, + ForwardMessage, + FileMessage, + ; + + fun toMessageKey(): MessageKey { + return when (this) { + Image -> net.mamoe.mirai.message.data.Image + LightApp -> net.mamoe.mirai.message.data.LightApp + Audio -> net.mamoe.mirai.message.data.Audio + FlashImage -> net.mamoe.mirai.message.data.FlashImage + MarketFace -> net.mamoe.mirai.message.data.MarketFace + Dice -> net.mamoe.mirai.message.data.Dice + ForwardMessage -> net.mamoe.mirai.message.data.ForwardMessage + FileMessage -> net.mamoe.mirai.message.data.FileMessage + } + } + } } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/CommandConfig.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/CommandConfig.kt index 50e60ec..ed63b05 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/CommandConfig.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/CommandConfig.kt @@ -16,19 +16,24 @@ */ package io.github.samarium150.mirai.plugin.driftbottle.config -import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value -object CommandConfig : AutoSavePluginConfig("Command") { +object CommandConfig : ReadOnlyPluginConfig("Command") { @ValueDescription("jump-into命令的别名") - val jumpInto: Array by value(arrayOf("跳海", "跳进海里")) + val jumpInto by value(arrayOf("跳海", "跳进海里")) @ValueDescription("pickup命令的别名") - val pickup: Array by value(arrayOf("捡漂流瓶")) + val pickup by value(arrayOf("捡漂流瓶")) @ValueDescription("throw-away命令的别名") - val throwAway: Array by value(arrayOf("丢漂流瓶")) + val throwAway by value(arrayOf("丢漂流瓶")) + @ValueDescription("comment命令的别名") + val comment by value(arrayOf("评论")) + + @ValueDescription("sea-operation命令的别名") + val seaOperation by value(arrayOf("海", "s")) } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/GeneralConfig.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/GeneralConfig.kt index 9c911e9..a7a643a 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/GeneralConfig.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/GeneralConfig.kt @@ -16,11 +16,11 @@ */ package io.github.samarium150.mirai.plugin.driftbottle.config -import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value -object GeneralConfig : AutoSavePluginConfig("General") { +object GeneralConfig : ReadOnlyPluginConfig("General") { @ValueDescription("捡起命令不减少漂流瓶总数") val incrementalBottle by value(true) @@ -39,4 +39,7 @@ object GeneralConfig : AutoSavePluginConfig("General") { @ValueDescription("随机延迟回复的时间区间 (单位: 毫秒)") val randomDelayInterval: Pair by value(Pair(1000L, 1500L)) + + @ValueDescription("使用转发消息展示漂流瓶") + val displayInForward by value(false) } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/ReplyConfig.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/ReplyConfig.kt index dea998e..e350ced 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/ReplyConfig.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/config/ReplyConfig.kt @@ -16,17 +16,17 @@ */ package io.github.samarium150.mirai.plugin.driftbottle.config -import net.mamoe.mirai.console.data.AutoSavePluginConfig +import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value -object ReplyConfig : AutoSavePluginConfig("Reply") { +object ReplyConfig : ReadOnlyPluginConfig("Reply") { @ValueDescription("海里没有物品时的回复") val noItem: String by value("海里暂时没有物品哦~") @ValueDescription("捡起漂流瓶的回复") - val pickupBottle: String by value("你在海边捡到了一个来自【%source】的漂流瓶,打开瓶子,里面有一张纸条,写着:\n") + val pickupBottle: String by value("序号: %index\n你在海边捡到了一个来自【%source】的漂流瓶,打开瓶子,里面有一张纸条,写着:\n") @ValueDescription("捡起尸体的回复") val pickupBody: String by value("海面飘来了【%who】的浮尸……\n他于【%time】") @@ -53,9 +53,14 @@ object ReplyConfig : AutoSavePluginConfig("Reply") { val waitForNextMessage: String by value("请把想说的话写在纸条上发送出来吧~") @ValueDescription("等待漂流瓶内容超时的回复") - val timeoutMessage: String by value("是不是没有还没有想到要写什么呢?那待会再找我也行哦") + val timeout: String by value("是不是没有还没有想到要写什么呢?那待会再找我也行哦") @ValueDescription("漂流瓶内容不合规的回复") - val invalidMessage: String by value("不要往瓶子里塞奇怪的东西哦~") + val invalid: String by value("不要往瓶子里塞奇怪的东西哦~") + @ValueDescription("漂流瓶使用速度过快的回复") + val inCooldown: String by value("好快啊,能不能慢一点") + + @ValueDescription("漂流瓶消息类型被禁用的回复") + val bannedMessageType: String by value("不要往瓶子里塞这种类型的消息哦~") } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/CommentData.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/CommentData.kt new file mode 100644 index 0000000..7c730d3 --- /dev/null +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/CommentData.kt @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2020-2021 Samarium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ +package io.github.samarium150.mirai.plugin.driftbottle.data + +import kotlinx.coroutines.sync.Mutex +import kotlinx.serialization.Serializable +import net.mamoe.mirai.console.data.AutoSavePluginData +import net.mamoe.mirai.console.data.value +import java.util.* +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * @author LaoLittle + */ +@Serializable +data class CommentData( + val senderId: Long, + val senderName: String, + val content: String, +) { + @Suppress("unused") + val timestamp: Long = Date().time + + companion object : AutoSavePluginData("Comment") { + val comments by value(mutableMapOf>()) + } +} + +private val mutex = Mutex() + +var isLocked = false + private set + +@OptIn(ExperimentalContracts::class) +suspend fun useLock(owner: Any? = null, action: () -> T): T { + contract { + callsInPlace(action, InvocationKind.EXACTLY_ONCE) + } + + mutex.lock(owner) + isLocked = true + try { + return action() + } finally { + isLocked = false + mutex.unlock(owner) + } +} + +val comments by CommentData.Companion::comments diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/Item.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/Item.kt index 69d8a0f..09b50a7 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/Item.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/data/Item.kt @@ -16,24 +16,17 @@ */ package io.github.samarium150.mirai.plugin.driftbottle.data -import io.github.samarium150.mirai.plugin.driftbottle.MiraiConsoleDriftBottle import io.github.samarium150.mirai.plugin.driftbottle.config.GeneralConfig import io.github.samarium150.mirai.plugin.driftbottle.config.ReplyConfig -import io.github.samarium150.mirai.plugin.driftbottle.util.CacheType -import io.github.samarium150.mirai.plugin.driftbottle.util.cacheFolderByType -import io.github.samarium150.mirai.plugin.driftbottle.util.saveFrom +import io.github.samarium150.mirai.plugin.driftbottle.data.Item.Type.BODY +import io.github.samarium150.mirai.plugin.driftbottle.data.Item.Type.BOTTLE +import io.github.samarium150.mirai.plugin.driftbottle.util.* import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.Image.Key.queryUrl -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.PlainText -import net.mamoe.mirai.message.data.buildMessageChain import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage -import java.io.FileNotFoundException import java.net.URL -import java.time.ZoneId -import java.time.format.DateTimeFormatter import java.util.* @Serializable @@ -45,10 +38,10 @@ class Item { } val type: Type - private val owner: Owner - private val source: Source? + val owner: Owner + val source: Source? private var content: String? = null - private val timestamp = Date().time + val timestamp = Date().time constructor(type: Type, owner: Owner, source: Source?) { this.type = type @@ -63,15 +56,10 @@ class Item { this.content = content } - suspend fun toMessageChain(contact: Contact): MessageChain { - return buildMessageChain { + suspend fun toMessage(contact: Contact, index: Int): Message { + val messageChain = buildMessageChain { when (type) { - Type.BOTTLE -> { - var from = "$owner" - if (source != null) - from = "${source}的" + from - else - from += "悄悄留下" + BOTTLE -> { var chainJson = content ?: throw NoSuchElementException("未知错误") if (GeneralConfig.cacheImage) Image.IMAGE_ID_REGEX.findAll(chainJson).forEach { @@ -81,20 +69,34 @@ class Item { val image = file.uploadAsImage(contact) if (imageId != image.imageId) chainJson = chainJson.replace(imageId, image.imageId) - }.onFailure { e -> - if (e is FileNotFoundException) runCatching { - val image = Image.fromId(imageId) - file.saveFrom(image.queryUrl()) - }.onFailure { - MiraiConsoleDriftBottle.logger.error(e) - } else - MiraiConsoleDriftBottle.logger.error(e) + }.onFailure { + val image = Image(imageId) + file.saveFrom(image.queryUrl()) } } - add(ReplyConfig.pickupBottle.replace("%source", from)) - add(MessageChain.deserializeFromJsonString(chainJson)) + if (!GeneralConfig.displayInForward) { + var from = owner.name + if (source != null) + from = "${source}的$from\n" + else + from += "悄悄留下" + var comments = "" + CommentData.comments[index]?.let { + if (it.isNotEmpty()) comments += "\n此漂流瓶的评论为" + it.forEach { each -> + comments += "\n${each.senderName}: ${each.content}" + } + } + add( + ReplyConfig.pickupBottle + .replace("%source", from) + .replace("%index", (index + 1).toString()) + ) + add(MessageChain.deserializeFromJsonString(chainJson)) + add(comments) // 本来想让用户自定义评论位置的,但是...摆了 + } else add(MessageChain.deserializeFromJsonString(chainJson)) } - Type.BODY -> { + BODY -> { val avatarStream = URL(owner.avatarUrl).openStream() val img = avatarStream.use { it.uploadAsImage(contact) } add(img) @@ -102,13 +104,7 @@ class Item { PlainText( ReplyConfig.pickupBody .replace("%who", owner.name) - .replace( - "%time", - Date(timestamp) - .toInstant() - .atZone(ZoneId.of("Asia/Shanghai")) - .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - ) + .replace("%time", timestamp.timestampToString()) ) ) val where = if (source != null) @@ -118,5 +114,19 @@ class Item { } } } + return if (!GeneralConfig.displayInForward || messageChain.any { + when (it) { + is Audio, is FlashImage -> true + else -> false + } + }) messageChain + else buildForwardMessage(contact, CustomForwardMsgDisplay(index + 1, this)) { + add(owner.id, owner.name, messageChain, timestamp.seconds) + CommentData.comments[index]?.let { + it.forEach { each -> + add(each.senderId, each.senderName, PlainText(each.content), each.timestamp.seconds) + } + } + } } } diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/ContentCensor.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/ContentCensor.kt index 8c01cb8..ae504d5 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/ContentCensor.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/ContentCensor.kt @@ -72,9 +72,7 @@ object ContentCensor { val response: ResponseBody = when (message) { is PlainText -> client.submitForm( url = "${URLS.TEXT_CENSOR.url}?access_token=${ContentCensorToken.accessToken}", - formParameters = Parameters.build { - append("text", message.content) - } + formParameters = parametersOf("text", message.content) ) is Image -> { val buffer = withContext(Dispatchers.IO) { @@ -87,9 +85,7 @@ object ContentCensor { val base64 = Base64.getEncoder().encodeToString(output.toByteArray()) client.submitForm( url = "${URLS.IMAGE_CENSOR.url}?access_token=${ContentCensorToken.accessToken}", - formParameters = Parameters.build { - append("image", base64) - } + formParameters = parametersOf("image", base64) ) } else -> continue diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/CustomForwardMsgDisplay.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/CustomForwardMsgDisplay.kt new file mode 100644 index 0000000..3ebaa0c --- /dev/null +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/CustomForwardMsgDisplay.kt @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2020-2021 Samarium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ +package io.github.samarium150.mirai.plugin.driftbottle.util + +import io.github.samarium150.mirai.plugin.driftbottle.data.Item +import net.mamoe.mirai.message.data.ForwardMessage.DisplayStrategy +import net.mamoe.mirai.message.data.RawForwardMessage + +/** + * @author LaoLittle + */ +class CustomForwardMsgDisplay( + private val index: Int, + private val item: Item +) : DisplayStrategy { + override fun generateBrief(forward: RawForwardMessage): String = "[漂流瓶]" + + override fun generateSummary(forward: RawForwardMessage): String = "漂流瓶序号: $index" + + override fun generatePreview(forward: RawForwardMessage): List { + val subject = if (item.source == null) "此漂流瓶由私聊发送" else "此漂流瓶来自群: ${item.source}" + val sender = "发送人是: ${item.owner.name}" + val message = "点击打开漂流瓶" + return listOf(subject, sender, message) + } +} diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/General.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/General.kt index d01071f..c3c8a7c 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/General.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/General.kt @@ -1,21 +1,48 @@ +/** + * Copyright (c) 2020-2021 Samarium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ package io.github.samarium150.mirai.plugin.driftbottle.util import io.github.samarium150.mirai.plugin.driftbottle.MiraiConsoleDriftBottle +import io.github.samarium150.mirai.plugin.driftbottle.MiraiConsoleDriftBottle.reload +import io.github.samarium150.mirai.plugin.driftbottle.MiraiConsoleDriftBottle.save import io.github.samarium150.mirai.plugin.driftbottle.config.AdvancedConfig import io.github.samarium150.mirai.plugin.driftbottle.config.GeneralConfig +import io.github.samarium150.mirai.plugin.driftbottle.data.Sea +import io.github.samarium150.mirai.plugin.driftbottle.data.comments +import io.github.samarium150.mirai.plugin.driftbottle.data.useLock import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import net.mamoe.mirai.console.command.CommandSender +import net.mamoe.mirai.console.data.ReadOnlyPluginConfig +import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.message.data.At -import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.message.data.PlainText -import net.mamoe.mirai.message.data.toMessageChain +import net.mamoe.mirai.message.data.* import java.io.BufferedOutputStream import java.io.File import java.io.FileOutputStream import java.net.URL +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.* +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract internal enum class CacheType { IMAGE @@ -61,3 +88,63 @@ internal fun disableAt(messageChain: MessageChain, subject: Contact): MessageCha chain.toMessageChain() } else messageChain } + +internal fun Stack.put(item: E): Stack { + addElement(item) + return this +} + +@OptIn(ConsoleExperimentalApi::class) +internal val forbidMessageKeys by lazy { + mutableListOf>().apply { + AdvancedConfig.saveMessageTypes.forEach { (type, bool) -> + if (!bool) add(type.toMessageKey()) + } + }.toTypedArray() +} + +internal fun ReadOnlyPluginConfig.alsoSave() { + reload() + save() +} + +@OptIn(ExperimentalContracts::class) +internal suspend fun CommandSender.isNotOutOfRange(index: Int?): Boolean { + contract { + returns(true) implies (index != null) + } + return when (index) { + null -> { + subject?.sendMessage("请尝试输入序号") ?: MiraiConsoleDriftBottle.logger.error("控制台使用请输入序号") + false + } + !in 0 until Sea.contents.size -> { + sendMessage("数字超出范围!") + false + } + else -> true + } +} + +internal val indexOfBottle = mutableMapOf>() + +internal fun Long.timestampToString(): String { + return Date(this) + .toInstant() + .atZone(ZoneId.of("Asia/Shanghai")) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) +} + +internal suspend fun rearrangeComments(from: Int) { + MiraiConsoleDriftBottle.launch { + useLock { + comments.remove(from) + comments.filter { (t, _) -> t > from }.forEach { (t, _) -> + comments[t - 1] = comments.getValue(t) + comments.remove(t) + } + } + } +} + +internal val Long.seconds get() = (this / 1000).toInt() diff --git a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/Throttle.kt b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/Throttle.kt index 29b4193..f2eeb66 100644 --- a/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/Throttle.kt +++ b/src/main/kotlin/io/github/samarium150/mirai/plugin/driftbottle/util/Throttle.kt @@ -1,3 +1,19 @@ +/** + * Copyright (c) 2020-2021 Samarium + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see + */ package io.github.samarium150.mirai.plugin.driftbottle.util import kotlinx.coroutines.sync.Mutex