From 80e1b53b635adfe55c976c403edc7e5eb5c60b3b Mon Sep 17 00:00:00 2001 From: sandtechnology Date: Fri, 18 Dec 2020 13:14:10 +0800 Subject: [PATCH 01/15] Update nudge template id --- .../protocol/packet/chat/receive/OnlinePush.ReqPush.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt index 354f5d5ab..c258eed1d 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt @@ -243,7 +243,7 @@ private object Transformers732 : Map by mapOf( val grayTip = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()).optGeneralGrayTip when (grayTip?.templId) { //戳一戳 - 10043L, 1134L, 1135L, 1136L -> { + 10043L, 1133L, 1132L, 1134L, 1135L, 1136L -> { //预置数据,服务器将不会提供己方已知消息 var action = "" var from: Member = group.botAsMember @@ -509,7 +509,7 @@ internal object Transformers528 : Map by mapOf( val body = vProtobuf.loadAs(Submsgtype0x122.Submsgtype0x122.MsgBody.serializer()) when (body.templId) { //戳一戳 - 1134L, 1135L, 1136L, 10043L -> { + 1132L, 1133L, 1134L, 1135L, 1136L, 10043L -> { //预置数据,服务器将不会提供己方已知消息 var from: Friend = bot.asFriend var action = "" From ea113dd246471cfa224ac35fcb4ecf9f960ff9c2 Mon Sep 17 00:00:00 2001 From: sandtechnology Date: Sat, 19 Dec 2020 01:45:19 +0800 Subject: [PATCH 02/15] Fix #577 --- .../src/commonMain/kotlin/message/data/Voice.kt | 1 + mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt | 2 +- .../src/commonMain/kotlin/message/conversions.kt | 2 +- .../packet/chat/receive/MessageSvc.PbSendMsg.kt | 10 +++++++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt b/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt index c24a600b8..7b354c85c 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt @@ -39,6 +39,7 @@ public class Voice @MiraiInternalApi constructor( public override val fileName: String, public override val md5: ByteArray, public override val fileSize: Long, + public val codec: Int = 0, private val _url: String ) : PttMessage() { diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index f9b2f1c30..9f052d6db 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -453,7 +453,7 @@ internal class GroupImpl( response.fileKey, codec ) - Voice("${md5.toUHexString("")}.amr", md5, content.size.toLong(), "") + Voice("${md5.toUHexString("")}.amr", md5, content.size.toLong(), codec, "") } } diff --git a/mirai-core/src/commonMain/kotlin/message/conversions.kt b/mirai-core/src/commonMain/kotlin/message/conversions.kt index 807fd4085..203166d58 100644 --- a/mirai-core/src/commonMain/kotlin/message/conversions.kt +++ b/mirai-core/src/commonMain/kotlin/message/conversions.kt @@ -248,7 +248,7 @@ internal fun MsgComm.Msg.toMessageChain( // 4 -> Voice(String(fileName), fileMd5, fileSize.toLong(),String(downPara)) // else -> null // } - Voice(String(fileName), fileMd5, fileSize.toLong(), String(downPara)) + Voice(String(fileName), fileMd5, fileSize.toLong(), format, String(downPara)) } return buildMessageChain(elements.size + 1 + if (pptMsg == null) 0 else 1) { diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt index 95a6d04c9..5ea80c590 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt @@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.PttMessage +import net.mamoe.mirai.message.data.Voice import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.contracts.InvocationKind import kotlin.contracts.contract @@ -145,7 +146,14 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Date: Sat, 19 Dec 2020 23:36:12 +0800 Subject: [PATCH 03/15] Auto-publish artifacts when uploaded to bintray --- buildSrc/src/main/kotlin/PublishingHelpers.kt | 3 +++ gradle/bintray.gradle | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/PublishingHelpers.kt b/buildSrc/src/main/kotlin/PublishingHelpers.kt index e1d11b63c..ad135444d 100644 --- a/buildSrc/src/main/kotlin/PublishingHelpers.kt +++ b/buildSrc/src/main/kotlin/PublishingHelpers.kt @@ -119,6 +119,9 @@ inline fun Project.configurePublishing( setPublications("mavenJava") setConfigurations("archives") + publish = true + override = true + pkg.apply { repo = bintrayRepo name = bintrayPkgName diff --git a/gradle/bintray.gradle b/gradle/bintray.gradle index a9c102777..6ef0cb138 100644 --- a/gradle/bintray.gradle +++ b/gradle/bintray.gradle @@ -10,7 +10,7 @@ publishing { repositories { maven { - url = "https://api.bintray.com/maven/him188moe/mirai/mirai-core/;publish=0" + url = "https://api.bintray.com/maven/him188moe/mirai/mirai-core/;publish=1" credentials { username = upload.Bintray.getUser(project) From 945bcc9b5bf924bd69ec8d90deda49cd1a299a39 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 08:04:56 +0800 Subject: [PATCH 04/15] Add MessageChain.findIsInstance --- .../src/commonMain/kotlin/message/data/MessageChain.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt index 8bbcd1c22..80d7e365b 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt @@ -157,12 +157,19 @@ public inline fun MessageChain.noneContent(block: (MessageContent) -> Boolean): } +/** + * 获取第一个 [M] 类型的 [Message] 实例 + */ +@JvmSynthetic +public inline fun MessageChain.findIsInstance(): M? = + this.find { it is M } as M? + /** * 获取第一个 [M] 类型的 [Message] 实例 */ @JvmSynthetic public inline fun MessageChain.firstIsInstanceOrNull(): M? = - this.firstOrNull { it is M } as M? + this.find { it is M } as M? /** * 获取第一个 [M] 类型的 [Message] 实例 From 79242ed48310261fe6f50c231ca5ab5c22a59e96 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 08:05:32 +0800 Subject: [PATCH 05/15] Add BuilderInference to selectMessages and whileSelectMessages --- mirai-core-api/src/commonMain/kotlin/event/select.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/mirai-core-api/src/commonMain/kotlin/event/select.kt b/mirai-core-api/src/commonMain/kotlin/event/select.kt index 079c50a5c..4be56a125 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/select.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/select.kt @@ -56,11 +56,12 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi * @see nextMessage 挂起协程并等待下一条消息 */ @Suppress("unused") +@BuilderInference public suspend inline fun T.whileSelectMessages( timeoutMillis: Long = -1, filterContext: Boolean = true, priority: Listener.EventPriority = EventPriority.MONITOR, - crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit + @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit ): Unit = whileSelectMessagesImpl(timeoutMillis, filterContext, priority, selectBuilder) /** @@ -68,11 +69,12 @@ public suspend inline fun T.whileSelectMessages( */ @MiraiExperimentalApi @JvmName("selectMessages1") +@BuilderInference public suspend inline fun T.selectMessagesUnit( timeoutMillis: Long = -1, filterContext: Boolean = true, priority: Listener.EventPriority = EventPriority.MONITOR, - crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit.() -> Unit + @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit.() -> Unit ): Unit = selectMessagesImpl(timeoutMillis, true, filterContext, priority, selectBuilder) @@ -97,12 +99,12 @@ public suspend inline fun T.selectMessagesUnit( * @see nextMessage 挂起协程并等待下一条消息 */ @Suppress("unused") // false positive -// @BuilderInference // https://youtrack.jetbrains.com/issue/KT-37716 +@BuilderInference public suspend inline fun T.selectMessages( timeoutMillis: Long = -1, filterContext: Boolean = true, priority: Listener.EventPriority = EventPriority.MONITOR, - // @BuilderInference + @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit ): R = selectMessagesImpl( From 540056acafb0a94927d8aaec4635eb41ca5272ee Mon Sep 17 00:00:00 2001 From: iceBear67 <48877375+iceBear67@users.noreply.github.com> Date: Sun, 20 Dec 2020 08:11:41 +0800 Subject: [PATCH 06/15] Update FAQ.md (#735) * Update FAQ.md Add NoSuchProviderException * Update FAQ.md * Update FAQ.md Co-authored-by: Karlatemp Co-authored-by: Him188 --- docs/FAQ.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/FAQ.md b/docs/FAQ.md index 591abf26b..8ac209592 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -26,10 +26,14 @@ mirai 已经可以用于各项工作,但我们并不保证完全的稳定。 阅读 [项目整体架构](mirai.md#%E9%A1%B9%E7%9B%AE%E6%95%B4%E4%BD%93%E6%9E%B6%E6%9E%84). **警告:** mirai 开发者自愿花费其休息时间,无偿维护 mirai 系列项目,**但没有义务提供任何方面的帮助**。 -使用者应秉持对开发者无私贡献的尊重,而不应该期望得到帮助。 +使用者应秉持对开发者无私贡献的尊重,而不应该期望得到帮助。 + +### 启动 Mirai 时遇到 java.security.NoSuchProviderException: JCE cannot authenticate the provider BC +请更换 OpenJDK。该问题是由于BouncyCastle在重打包后丢失Jar签名引起的。 +~~多发于Oracle JDK~~ ## 开发相关 > 欢迎帮助维护这个问题列表: 提交 PR 或者 issue. -... 待补充 \ No newline at end of file +... 待补充 From d51b268c1552ddc69be7856eea74a47ca54e8f81 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 08:21:50 +0800 Subject: [PATCH 07/15] Kill bot on returnCode <=-10000, fix #691 --- mirai-core-api/src/commonMain/kotlin/event/events/bot.kt | 3 ++- mirai-core/src/commonMain/kotlin/AbstractBot.kt | 2 +- .../kotlin/network/QQAndroidBotNetworkHandler.kt | 6 +++--- .../kotlin/network/protocol/packet/PacketFactory.kt | 8 +++++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/bot.kt b/mirai-core-api/src/commonMain/kotlin/event/events/bot.kt index da1936a9d..c9bdc5582 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/bot.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/bot.kt @@ -73,7 +73,8 @@ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() { * 因 returnCode = -10008 等原因掉线 */ @MiraiInternalApi("This is very experimental and might be changed") - public data class PacketFactory10008 internal constructor( + public data class PacketFactoryErrorCode internal constructor( + val returnCode: Int, public override val bot: Bot, public override val cause: Throwable ) : BotOfflineEvent(), Packet, BotPassiveEvent, CauseAware diff --git a/mirai-core/src/commonMain/kotlin/AbstractBot.kt b/mirai-core/src/commonMain/kotlin/AbstractBot.kt index 4b5449d9d..f5c5bacf1 100644 --- a/mirai-core/src/commonMain/kotlin/AbstractBot.kt +++ b/mirai-core/src/commonMain/kotlin/AbstractBot.kt @@ -103,7 +103,7 @@ internal abstract class AbstractBot constructor( is BotOfflineEvent.MsfOffline, is BotOfflineEvent.Dropped, is BotOfflineEvent.RequireReconnect, - is BotOfflineEvent.PacketFactory10008 + is BotOfflineEvent.PacketFactoryErrorCode -> { if (!_network.isActive) { // normally closed diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt index e35226563..fba5eacb1 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt @@ -30,7 +30,7 @@ import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.* -import net.mamoe.mirai.internal.network.protocol.packet.KnownPacketFactories.PacketFactoryIllegalState10008Exception +import net.mamoe.mirai.internal.network.protocol.packet.KnownPacketFactories.PacketFactoryIllegalStateException import net.mamoe.mirai.internal.network.protocol.packet.chat.GroupInfoImpl import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList @@ -443,9 +443,9 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo input.use { try { parsePacket(it) - } catch (e: PacketFactoryIllegalState10008Exception) { + } catch (e: PacketFactoryIllegalStateException) { logger.warning { "Network force offline: ${e.message}" } - bot.launch { BotOfflineEvent.PacketFactory10008(bot, e).broadcast() } + bot.launch { BotOfflineEvent.PacketFactoryErrorCode(e.code, bot, e).broadcast() } } } } diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt index 3c6f9b938..8353ee2b3 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -173,7 +173,8 @@ internal object KnownPacketFactories { ?: IncomingFactories.firstOrNull { it.receivingCommandName == commandName } } - class PacketFactoryIllegalState10008Exception @JvmOverloads constructor( + class PacketFactoryIllegalStateException @JvmOverloads constructor( + val code: Int, override val message: String? = null, override val cause: Throwable? = null ) : RuntimeException() @@ -310,8 +311,9 @@ internal object KnownPacketFactories { val returnCode = readInt() check(returnCode == 0) { - if (returnCode == -10008) { // https://github.com/mamoe/mirai/issues/470 - throw PacketFactoryIllegalState10008Exception("returnCode = $returnCode") + if (returnCode <= -10000) { + // https://github.com/mamoe/mirai/issues/470 + throw PacketFactoryIllegalStateException(returnCode, "returnCode = $returnCode") } else "returnCode = $returnCode" } From 56e7d4de3d70caf757cab964e298a931fd99e752 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 08:42:34 +0800 Subject: [PATCH 08/15] Fix MessageRecallEvent.FriendRecall.authorId, close #704; Add MessageRecallEvent.author --- .../commonMain/kotlin/event/events/message.kt | 37 +++++++++++++++---- .../packet/chat/receive/OnlinePush.ReqPush.kt | 4 +- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt index 2cc433812..984c65fdc 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt @@ -262,6 +262,11 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { */ public abstract val authorId: Long + /** + * 消息原发送人, 为 `null` 表示原消息发送人已经不是 bot 的好友或已经被移出群。 + */ + public abstract val author: UserOrBot? + /** * 消息 ids. * @see MessageSource.ids @@ -282,7 +287,7 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { /** * 好友消息撤回事件 */ - public data class FriendRecall internal constructor( + public data class FriendRecall @MiraiInternalApi public constructor( public override val bot: Bot, public override val messageIds: IntArray, public override val messageInternalIds: IntArray, @@ -290,11 +295,25 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { /** * 撤回操作人, 好友的 [User.id] */ - public val operator: Long + public val operatorId: Long ) : MessageRecallEvent(), Packet { - public override val authorId: Long - get() = bot.id + @Deprecated("Binary compatibility.", level = DeprecationLevel.HIDDEN) + public fun getOperator(): Long = operatorId + /** + * 撤回操作人. 为 `null` 表示该用户已经不是 bot 的好友 + */ + public val operator: Friend? get() = bot.getFriend(operatorId) + + /** + * 消息原发送人, 等于 [operator] + */ + override val author: Friend? get() = operator + + public override val authorId: Long + get() = operatorId + + @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -305,7 +324,7 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { if (!messageIds.contentEquals(other.messageIds)) return false if (!messageInternalIds.contentEquals(other.messageInternalIds)) return false if (messageTime != other.messageTime) return false - if (operator != other.operator) return false + if (operatorId != other.operatorId) return false return true } @@ -315,7 +334,7 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { result = 31 * result + messageIds.contentHashCode() result = 31 * result + messageInternalIds.contentHashCode() result = 31 * result + messageTime - result = 31 * result + operator.hashCode() + result = 31 * result + operatorId.hashCode() return result } } @@ -335,6 +354,8 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { public override val operator: Member?, public override val group: Group ) : MessageRecallEvent(), GroupOperableEvent, Packet { + override val author: NormalMember? get() = group[authorId] + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -365,10 +386,12 @@ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { } } +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN) public val MessageRecallEvent.GroupRecall.author: Member get() = if (authorId == bot.id) group.botAsMember else group.getOrFail(authorId) -public val MessageRecallEvent.FriendRecall.isByBot: Boolean get() = this.operator == bot.id +public val MessageRecallEvent.FriendRecall.isByBot: Boolean get() = this.operatorId == bot.id // val MessageRecallEvent.GroupRecall.isByBot: Boolean get() = (this as GroupOperableEvent).isByBot // no need diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt index c258eed1d..0923ec644 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt @@ -422,6 +422,8 @@ internal object Transformers528 : Map by mapOf( @ProtoNumber(7) val pkgNum: Int, // 1 @ProtoNumber(8) val pkgIndex: Int, // 0 @ProtoNumber(9) val devSeq: Int, // 0 + @ProtoNumber(10) val unknown1: Int = 0, // ? 补 id + @ProtoNumber(11) val unknown2: Int = 0, // ? @ProtoNumber(12) val flag: Int // 1 ) : ProtoBuf @@ -441,7 +443,7 @@ internal object Transformers528 : Map by mapOf( messageIds = intArrayOf(it.srcId), messageInternalIds = intArrayOf(it.srcInternalId), messageTime = it.time, - operator = it.fromUin + operatorId = it.fromUin ) } }, From e3b553b4de233af5354ff20cef7ca0c914700e66 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 09:19:22 +0800 Subject: [PATCH 09/15] Improve LoginSolver, close #703: - Remove DefaultLoginSolver (originally experimental API) - Add docs - No default instance for Android platform - LoginSolver.Default is nullable now (in case on Android platform) - BotConfiguration.loginSolver is nullable now (meaning not provided by the user) --- build.gradle.kts | 5 +- .../kotlin/network/LoginFailedException.kt | 2 +- .../kotlin/utils/BotConfiguration.kt | 15 +++- .../commonMain/kotlin/utils/LoginSolver.kt | 84 ++++++++++++------- .../commonMain/kotlin/utils/SwingSolver.kt | 64 +++++++------- .../network/QQAndroidBotNetworkHandler.kt | 17 +++- 6 files changed, 118 insertions(+), 69 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 62dc63a6c..0ffa2d12a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -141,7 +141,10 @@ fun Project.configureJvmTarget() { } kotlinTargets.orEmpty().filterIsInstance().forEach { target -> - target.compilations.all { kotlinOptions.jvmTarget = "1.8" } + target.compilations.all { + kotlinOptions.jvmTarget = "1.8" + kotlinOptions.languageVersion = "1.4" + } target.testRuns["test"].executionTask.configure { useJUnitPlatform() } } diff --git a/mirai-core-api/src/commonMain/kotlin/network/LoginFailedException.kt b/mirai-core-api/src/commonMain/kotlin/network/LoginFailedException.kt index b08e208ff..98c2e60af 100644 --- a/mirai-core-api/src/commonMain/kotlin/network/LoginFailedException.kt +++ b/mirai-core-api/src/commonMain/kotlin/network/LoginFailedException.kt @@ -52,7 +52,7 @@ public class RetryLaterException @MiraiInternalApi constructor() : * 无标准输入或 Kotlin 不支持此输入. */ public class NoStandardInputForCaptchaException @MiraiInternalApi constructor( - public override val cause: Throwable? + public override val cause: Throwable? = null ) : LoginFailedException(true, "no standard input for captcha") /** diff --git a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt index f897fffe0..66bd46767 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt @@ -90,8 +90,17 @@ public open class BotConfiguration { // open for Java /** 最多尝试多少次重连 */ public var reconnectionRetryTimes: Int = Int.MAX_VALUE - /** 验证码处理器 */ - public var loginSolver: LoginSolver = LoginSolver.Default + /** + * 验证码处理器 + * + * - 在 Android 需要手动提供 [LoginSolver] + * - 在 JVM, Mirai 会根据环境支持情况选择 Swing/CLI 实现 + * + * 详见 [LoginSolver.Default] + * + * @see LoginSolver + */ + public var loginSolver: LoginSolver? = LoginSolver.Default /** 使用协议类型 */ public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE @@ -115,6 +124,7 @@ public open class BotConfiguration { // open for Java Json { isLenient = true ignoreUnknownKeys = true + prettyPrint = true } }.getOrElse { Json {} } @@ -133,6 +143,7 @@ public open class BotConfiguration { // open for Java * * @see deviceInfo */ + @ConfigurationDsl public fun loadDeviceInfoJson(json: String) { deviceInfo = { this.json.decodeFromString(DeviceInfo.serializer(), json) diff --git a/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt b/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt index 61ff25e7a..20266a5ec 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt @@ -20,6 +20,8 @@ import kotlinx.coroutines.withContext import net.mamoe.mirai.Bot import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.NoStandardInputForCaptchaException +import net.mamoe.mirai.utils.LoginSolver.Companion.Default +import net.mamoe.mirai.utils.StandardCharImageLoginSolver.Companion.createBlocking import java.awt.Image import java.awt.image.BufferedImage import java.io.File @@ -29,6 +31,9 @@ import kotlin.coroutines.CoroutineContext /** * 验证码, 设备锁解决器 + * + * @see Default + * @see BotConfiguration.loginSolver */ public abstract class LoginSolver { /** @@ -61,46 +66,43 @@ public abstract class LoginSolver { public abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? public companion object { - public val Default: LoginSolver = kotlin.run { - if (WindowHelperJvm.isDesktopSupported) { - SwingSolver - } else { - DefaultLoginSolver({ readLine() ?: throw NoStandardInputForCaptchaException(null) }) - } + /** + * 当前平台默认的 [LoginSolver]。 + * + * 检测策略: + * 1. 检测 `android.util.Log`, 如果存在, 返回 `null`. + * 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 [] + * 2. 检测 JVM 桌面环境, + * + * 在桌面 JVM, Mirai 会检测 Java Swing, 在可用时首选 [SwingSolver]. 可以通过 `System.setProperty("mirai.no-desktop", "true")` 关闭 + * 在 Android, mirai 检测 `android.util.Log`. 然后 + * + * @return [SwingSolver] 或 + */ + @JvmField + public val Default: LoginSolver? = when (WindowHelperJvm.platformKind) { + WindowHelperJvm.PlatformKind.ANDROID -> null + WindowHelperJvm.PlatformKind.SWING -> SwingSolver + WindowHelperJvm.PlatformKind.CLI -> StandardCharImageLoginSolver() } - } -} - -/** - * 自动选择 [SwingSolver] 或 [StandardCharImageLoginSolver] - */ -@MiraiExperimentalApi -public class DefaultLoginSolver( - public val input: suspend () -> String, - overrideLogger: MiraiLogger? = null -) : LoginSolver() { - private val delegate: LoginSolver = StandardCharImageLoginSolver(input, overrideLogger) - - override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? { - return delegate.onSolvePicCaptcha(bot, data) - } - - override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { - return delegate.onSolveSliderCaptcha(bot, url) - } - - override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { - return delegate.onSolveUnsafeDeviceLoginVerify(bot, url) + @Suppress("unused") + @Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN) + public fun getDefault(): LoginSolver = Default + ?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver") } } /** + * CLI 环境 [LoginSolver]. 将验证码图片转为字符画并通过 `output` 输出, [input] 获取用户输入. + * * 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [overrideLogger] 输出 + * + * @see createBlocking */ @MiraiExperimentalApi public class StandardCharImageLoginSolver( - input: suspend () -> String, + input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() }, /** * 为 `null` 时使用 [Bot.logger] */ @@ -166,6 +168,28 @@ public class StandardCharImageLoginSolver( logger.info("正在提交中...") } } + + public companion object { + /** + * 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver] + * + * @param input 将在协程 IO 池执行, 可以有阻塞调用 + */ + @JvmStatic + public fun createBlocking(input: () -> String, output: MiraiLogger?): StandardCharImageLoginSolver { + return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } }, output) + } + + /** + * 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver] + * + * @param input 将在协程 IO 池执行, 可以有阻塞调用 + */ + @JvmStatic + public fun createBlocking(input: () -> String): StandardCharImageLoginSolver { + return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } }) + } + } } /////////////////////////////// diff --git a/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt b/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt index 2271f94d6..a7a56cfc8 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt @@ -70,39 +70,39 @@ public object SwingSolver : LoginSolver() { // 不会触发各种 NoDefClassError @Suppress("DEPRECATION") internal object WindowHelperJvm { - internal val isDesktopSupported: Boolean = kotlin.run { - if (System.getProperty("mirai.no-desktop") === null) { - kotlin.runCatching { - Class.forName("java.awt.Desktop") - Class.forName("java.awt.Toolkit") - }.onFailure { return@run false } // Android OS - kotlin.runCatching { - Toolkit.getDefaultToolkit() - }.onFailure { // AWT Error, #270 - return@run false + enum class PlatformKind { + ANDROID, + SWING, + CLI + } + + internal val platformKind: PlatformKind = kotlin.run { + if (kotlin.runCatching { Class.forName("android.util.Log") }.isSuccess) { + // Android platform + return@run PlatformKind.ANDROID + } + kotlin.runCatching { + Class.forName("java.awt.Desktop") + Class.forName("java.awt.Toolkit") + Toolkit.getDefaultToolkit() + + if (Desktop.isDesktopSupported()) { + MiraiLogger.TopLevel.info( + """ + Mirai 正在使用桌面环境. 如遇到验证码将会弹出对话框. 可添加 JVM 属性 `mirai.no-desktop` 以关闭. + """.trimIndent() + ) + MiraiLogger.TopLevel.info( + """ + Mirai is using desktop. Captcha will be thrown by window popup. You can add `mirai.no-desktop` to JVM properties (-Dmirai.no-desktop) to disable it. + """.trimIndent() + ) + return@run PlatformKind.SWING + } else { + return@run PlatformKind.CLI } - kotlin.runCatching { - Desktop.isDesktopSupported().also { stat -> - if (stat) { - MiraiLogger.TopLevel.info( - """ - Mirai 正在使用桌面环境. 如遇到验证码将会弹出对话框. 可添加 JVM 属性 `mirai.no-desktop` 以关闭. - """.trimIndent() - ) - MiraiLogger.TopLevel.info( - """ - Mirai is using desktop. Captcha will be thrown by window popup. You can add `mirai.no-desktop` to JVM properties (-Dmirai.no-desktop) to disable it. - """.trimIndent() - ) - } - } - }.getOrElse { - // Should not happen - MiraiLogger.TopLevel.warning("Exception in checking desktop support.", it) - false - } - } else { - false + }.getOrElse { + return@run PlatformKind.CLI } } } diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt index fba5eacb1..d38e9075f 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt @@ -152,17 +152,28 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo logger.info { "Connected to server $host:$port" } startPacketReceiverJobOrKill(CancellationException("relogin", cause)) + fun LoginSolver?.notnull(): LoginSolver { + checkNotNull(this) { + "No LoginSolver found. Please provide by BotConfiguration.loginSolver. " + + "For example use `BotFactory.newBot(...) { loginSolver = yourLoginSolver}` in Kotlin, " + + "use `BotFactory.newBot(..., new BotConfiguration() {{ setLoginSolver(yourLoginSolver) }})` in Java." + } + return this + } + + fun loginSolverNotNull() = bot.configuration.loginSolver.notnull() + var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect() mainloop@ while (true) { when (response) { is WtLogin.Login.LoginPacketResponse.UnsafeLogin -> { - bot.configuration.loginSolver.onSolveUnsafeDeviceLoginVerify(bot, response.url) + loginSolverNotNull().onSolveUnsafeDeviceLoginVerify(bot, response.url) response = WtLogin.Login.SubCommand9(bot.client).sendAndExpect() } is WtLogin.Login.LoginPacketResponse.Captcha -> when (response) { is WtLogin.Login.LoginPacketResponse.Captcha.Picture -> { - var result = bot.configuration.loginSolver.onSolvePicCaptcha(bot, response.data) + var result = loginSolverNotNull().onSolvePicCaptcha(bot, response.data) if (result == null || result.length != 4) { //refresh captcha result = "ABCD" @@ -172,7 +183,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo continue@mainloop } is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> { - val ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url).orEmpty() + val ticket = loginSolverNotNull().onSolveSliderCaptcha(bot, response.url).orEmpty() response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect() continue@mainloop } From 9d8e0ebf02fd3d69be68ef5d6a30fb28a5bf18fd Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 09:44:45 +0800 Subject: [PATCH 10/15] Improve StandardCharImageLoginSolver: - Add loggerSupplier - Improve log message --- .../commonMain/kotlin/utils/LoginSolver.kt | 64 +++++++++++-------- 1 file changed, 36 insertions(+), 28 deletions(-) diff --git a/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt b/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt index 20266a5ec..f83715ad2 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt @@ -96,76 +96,84 @@ public abstract class LoginSolver { /** * CLI 环境 [LoginSolver]. 将验证码图片转为字符画并通过 `output` 输出, [input] 获取用户输入. * - * 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [overrideLogger] 输出 + * 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [loggerSupplier] 输出 * * @see createBlocking */ -@MiraiExperimentalApi -public class StandardCharImageLoginSolver( +public class StandardCharImageLoginSolver @JvmOverloads constructor( input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() }, /** * 为 `null` 时使用 [Bot.logger] */ - private val overrideLogger: MiraiLogger? = null + private val loggerSupplier: (bot: Bot) -> MiraiLogger = { it.logger } ) : LoginSolver() { + public constructor( + input: suspend () -> String = { readLine() ?: throw NoStandardInputForCaptchaException() }, + overrideLogger: MiraiLogger? + ) : this(input, { overrideLogger ?: it.logger }) + private val input: suspend () -> String = suspend { withContext(Dispatchers.IO) { input() } } override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock { - val logger = overrideLogger ?: bot.logger + val logger = loggerSupplier(bot) @Suppress("BlockingMethodInNonBlockingContext") withContext(Dispatchers.IO) { val tempFile: File = File.createTempFile("tmp", ".png").apply { deleteOnExit() } tempFile.createNewFile() - logger.info("需要图片验证码登录, 验证码为 4 字母") + logger.info { "[PicCaptcha] 需要图片验证码登录, 验证码为 4 字母" } + logger.info { "[PicCaptcha] Picture captcha required. Captcha consists of 4 letters." } try { tempFile.writeChannel().apply { writeFully(data); close() } - logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}") + logger.info { "[PicCaptcha] 将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}" } + logger.info { "[PicCaptcha] Displaying char-image. If not clear, view file ${tempFile.absolutePath}" } } catch (e: Exception) { - logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") + logger.warning("[PicCaptcha] 无法写出验证码文件, 请尝试查看以上字符图片", e) + logger.warning("[PicCaptcha] Failed to export captcha image. Please see the char-image.", e) } - tempFile.inputStream().use { + tempFile.inputStream().use { stream -> try { - val img = ImageIO.read(it) + val img = ImageIO.read(stream) if (img == null) { - logger.info("无法创建字符图片. 请查看文件") + logger.warning { "[PicCaptcha] 无法创建字符图片. 请查看文件" } + logger.warning { "[PicCaptcha] Failed to create char-image. Please see the file." } } else { - logger.info("\n" + img.createCharImg()) + logger.info { "[PicCaptcha] \n" + img.createCharImg() } } } catch (throwable: Throwable) { - logger.info("创建字符图片时出错($throwable)。请查看文件") + logger.warning("[PicCaptcha] 创建字符图片时出错. 请查看文件.", throwable) + logger.warning("[PicCaptcha] Failed to create char-image. Please see the file.", throwable) } } } - logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") + logger.info { "[PicCaptcha] 请输入 4 位字母验证码. 若要更换验证码, 请直接回车" } + logger.info { "[PicCaptcha] Please type 4-letter captcha. Press Enter directly to refresh." } return input().takeUnless { it.isEmpty() || it.length != 4 }.also { - logger.info("正在提交[$it]中...") + logger.info { "[PicCaptcha] 正在提交 $it..." } + logger.info { "[PicCaptcha] Submitting $it..." } } } override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String = loginSolverLock.withLock { - val logger = overrideLogger ?: bot.logger - logger.info("需要滑动验证码") - logger.info("请在任意浏览器中打开以下链接并完成验证码. ") - logger.info("完成后请输入任意字符 ") + val logger = loggerSupplier(bot) + logger.info { "[SliderCaptcha] 需要滑动验证码, 请在任意浏览器中打开以下链接并完成验证码, 完成后请输入任意字符." } + logger.info { "[SliderCaptcha] Slider captcha required, please open the following link in any browser and solve the captcha. Type anything here after completion." } logger.info(url) return input().also { - logger.info("正在提交中...") + logger.info { "[SliderCaptcha] 正在提交中..." } + logger.info { "[SliderCaptcha] Submitting..." } } } override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String = loginSolverLock.withLock { - val logger = overrideLogger ?: bot.logger - logger.info("需要进行账户安全认证") - logger.info("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题") - logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次") - logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符") - logger.info("这步操作将在后续的版本中优化") - logger.info(url) + val logger = loggerSupplier(bot) + logger.info { "[UnsafeLogin] 当前登录环境不安全,服务器要求账户认证。请在 QQ 浏览器打开 $url 并完成验证后输入任意字符。" } + logger.info { "[UnsafeLogin] Account verification required by the server. Please open $url in QQ browser and complete challenge, then type anything here to submit." } return input().also { - logger.info("正在提交中...") + logger.info { "[UnsafeLogin] 正在提交中..." } + logger.info { "[UnsafeLogin] Submitting..." } } } From 51703eb8ba852ed5b99149fdf4a2aa916a42629a Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 09:46:02 +0800 Subject: [PATCH 11/15] Check mirai.no-desktop --- mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt b/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt index a7a56cfc8..7a504d374 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/SwingSolver.kt @@ -81,6 +81,7 @@ internal object WindowHelperJvm { // Android platform return@run PlatformKind.ANDROID } + if (System.getProperty("mirai.no-desktop") != null) return@run PlatformKind.CLI kotlin.runCatching { Class.forName("java.awt.Desktop") Class.forName("java.awt.Toolkit") From 565abae6718d78e446178f99c843611ad6accb15 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 20 Dec 2020 09:46:14 +0800 Subject: [PATCH 12/15] Improve docs --- .../src/commonMain/kotlin/utils/LoginSolver.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt b/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt index f83715ad2..8a097e707 100644 --- a/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt +++ b/mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt @@ -71,13 +71,11 @@ public abstract class LoginSolver { * * 检测策略: * 1. 检测 `android.util.Log`, 如果存在, 返回 `null`. - * 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 [] - * 2. 检测 JVM 桌面环境, + * 2. 检测 JVM 属性 `mirai.no-desktop`. 若存在, 返回 [StandardCharImageLoginSolver] + * 3. 检测 JVM 桌面环境, 若支持, 返回 [SwingSolver] + * 4. 返回 [StandardCharImageLoginSolver] * - * 在桌面 JVM, Mirai 会检测 Java Swing, 在可用时首选 [SwingSolver]. 可以通过 `System.setProperty("mirai.no-desktop", "true")` 关闭 - * 在 Android, mirai 检测 `android.util.Log`. 然后 - * - * @return [SwingSolver] 或 + * @return [SwingSolver] 或 [StandardCharImageLoginSolver] 或 `null` */ @JvmField public val Default: LoginSolver? = when (WindowHelperJvm.platformKind) { From c934ff5b8942541ad72fa4960ee7c1d45a6dc253 Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Dec 2020 10:49:17 +0800 Subject: [PATCH 13/15] Use ConcurrentLinkedQueue for EventSystem, #630 --- .../event/internal/InternalEventListeners.kt | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/mirai-core-api/src/commonMain/kotlin/event/internal/InternalEventListeners.kt b/mirai-core-api/src/commonMain/kotlin/event/internal/InternalEventListeners.kt index 259531dbd..f852c203e 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/internal/InternalEventListeners.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/internal/InternalEventListeners.kt @@ -14,9 +14,9 @@ import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.event.* import net.mamoe.mirai.event.events.BotEvent -import net.mamoe.mirai.utils.LockFreeLinkedList import net.mamoe.mirai.utils.MiraiLogger import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.reflect.KClass @@ -26,7 +26,7 @@ internal fun , E : Event> KClass.subscribeInternal(listen with(GlobalEventListeners[listener.priority]) { @Suppress("UNCHECKED_CAST") val node = ListenerRegistry(listener as Listener, this@subscribeInternal) - addLast(node) + add(node) listener.invokeOnCompletion { this.remove(node) } @@ -98,18 +98,18 @@ internal class ListenerRegistry( internal object GlobalEventListeners { - private val ALL_LEVEL_REGISTRIES: Map> + private val ALL_LEVEL_REGISTRIES: Map> init { val map = - EnumMap>(Listener.EventPriority::class.java) + EnumMap>(Listener.EventPriority::class.java) EventPriority.values().forEach { - map[it] = LockFreeLinkedList() + map[it] = ConcurrentLinkedQueue() } this.ALL_LEVEL_REGISTRIES = map } - operator fun get(priority: Listener.EventPriority): LockFreeLinkedList = + operator fun get(priority: Listener.EventPriority): ConcurrentLinkedQueue = ALL_LEVEL_REGISTRIES[priority]!! } @@ -121,54 +121,56 @@ internal suspend inline fun AbstractEvent.broadcastInternal() { callAndRemoveIfRequired(this@broadcastInternal) } +internal inline fun > T.forEach0(block: T.(E) -> Unit) { + forEach { block(it) } +} + @Suppress("DuplicatedCode") internal suspend inline fun callAndRemoveIfRequired( event: E ) { for (p in Listener.EventPriority.prioritiesExcludedMonitor) { - GlobalEventListeners[p].forEachNode { registeredRegistryNode -> + GlobalEventListeners[p].forEach0 { registeredRegistry -> if (event.isIntercepted) { return } - val listenerRegistry = registeredRegistryNode.nodeValue - if (!listenerRegistry.type.isInstance(event)) return@forEachNode - val listener = listenerRegistry.listener + if (!registeredRegistry.type.isInstance(event)) return@forEach0 + val listener = registeredRegistry.listener when (listener.concurrencyKind) { Listener.ConcurrencyKind.LOCKED -> { (listener as Handler).lock!!.withLock { if (listener.onEvent(event) == ListeningStatus.STOPPED) { - removeNode(registeredRegistryNode) + remove(registeredRegistry) } } } Listener.ConcurrencyKind.CONCURRENT -> { if (listener.onEvent(event) == ListeningStatus.STOPPED) { - removeNode(registeredRegistryNode) + remove(registeredRegistry) } } } } } coroutineScope { - GlobalEventListeners[EventPriority.MONITOR].forEachNode { registeredRegistryNode -> + GlobalEventListeners[EventPriority.MONITOR].forEach0 { registeredRegistry -> if (event.isIntercepted) { return@coroutineScope } - val listenerRegistry = registeredRegistryNode.nodeValue - if (!listenerRegistry.type.isInstance(event)) return@forEachNode - val listener = listenerRegistry.listener + if (!registeredRegistry.type.isInstance(event)) return@forEach0 + val listener = registeredRegistry.listener launch { when (listener.concurrencyKind) { Listener.ConcurrencyKind.LOCKED -> { (listener as Handler).lock!!.withLock { if (listener.onEvent(event) == ListeningStatus.STOPPED) { - removeNode(registeredRegistryNode) + remove(registeredRegistry) } } } Listener.ConcurrencyKind.CONCURRENT -> { if (listener.onEvent(event) == ListeningStatus.STOPPED) { - removeNode(registeredRegistryNode) + remove(registeredRegistry) } } } From 28249b317c5b9975a5d0cff1c552261ab4c8ad0c Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Dec 2020 15:40:25 +0800 Subject: [PATCH 14/15] Support parsing fragmented message. close #440 --- .../commonMain/kotlin/contact/GroupImpl.kt | 3 + .../commonMain/kotlin/message/conversions.kt | 42 ++++++++-- .../kotlin/message/incomingSourceImpl.kt | 81 ++++++++++--------- .../kotlin/message/offlineSourceImpl.kt | 26 +++--- .../chat/receive/OnlinePush.PbPushGroupMsg.kt | 18 +++-- .../packet/chat/receive/OnlinePush.ReqPush.kt | 31 +++---- .../kotlin/utils/GroupPkgMsgParsingCache.kt | 56 +++++++++++++ 7 files changed, 179 insertions(+), 78 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 9f052d6db..82296ee5f 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcP import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToGroup import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService +import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache import net.mamoe.mirai.internal.utils.MiraiPlatformUtils import net.mamoe.mirai.internal.utils.estimateLength import net.mamoe.mirai.internal.utils.toUHexString @@ -460,4 +461,6 @@ internal class GroupImpl( override fun toString(): String = "Group($id)" + + val groupPkgMsgParsingCache = GroupPkgMsgParsingCache() } diff --git a/mirai-core/src/commonMain/kotlin/message/conversions.kt b/mirai-core/src/commonMain/kotlin/message/conversions.kt index 203166d58..6ae3fba09 100644 --- a/mirai-core/src/commonMain/kotlin/message/conversions.kt +++ b/mirai-core/src/commonMain/kotlin/message/conversions.kt @@ -23,6 +23,7 @@ import net.mamoe.mirai.contact.Group import net.mamoe.mirai.internal.network.protocol.data.proto.HummerCommelem import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray @@ -234,24 +235,52 @@ internal fun MsgComm.Msg.toMessageChain( isTemp: Boolean = false ): MessageChain = toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp) +internal fun List.toMessageChain( + bot: Bot, + groupIdOrZero: Long, + onlineSource: Boolean, + isTemp: Boolean = false +): MessageChain = toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp) + +@JvmName("toMessageChain1") +internal fun List.toMessageChain( + bot: Bot?, + botId: Long, + groupIdOrZero: Long, + onlineSource: Boolean, + isTemp: Boolean = false +): MessageChain = map{it.msg}.toMessageChain(bot, botId, groupIdOrZero, onlineSource, isTemp) + internal fun MsgComm.Msg.toMessageChain( bot: Bot?, botId: Long, groupIdOrZero: Long, onlineSource: Boolean, isTemp: Boolean = false -): MessageChain { - val elements = this.msgBody.richText.elems +): MessageChain = listOf(this).toMessageChain(bot, botId, groupIdOrZero, onlineSource, isTemp) - val pptMsg = msgBody.richText.ptt?.run { +internal fun List.toMessageChain( + bot: Bot?, + botId: Long, + groupIdOrZero: Long, + onlineSource: Boolean, + isTemp: Boolean = false +): MessageChain { + val elements = this.flatMap { it.msgBody.richText.elems } + + @OptIn(ExperimentalStdlibApi::class) + val ppts = buildList { + this@toMessageChain.forEach { msg -> + msg.msgBody.richText.ptt?.run { // when (fileType) { // 4 -> Voice(String(fileName), fileMd5, fileSize.toLong(),String(downPara)) // else -> null // } - Voice(String(fileName), fileMd5, fileSize.toLong(), format, String(downPara)) + add(Voice(String(fileName), fileMd5, fileSize.toLong(), format, String(downPara))) + } + } } - - return buildMessageChain(elements.size + 1 + if (pptMsg == null) 0 else 1) { + return buildMessageChain(elements.size + 1 + ppts.size) { if (onlineSource) { checkNotNull(bot) { "bot is null" } when { @@ -263,7 +292,6 @@ internal fun MsgComm.Msg.toMessageChain( +OfflineMessageSourceImplByMsg(this@toMessageChain, botId) } elements.joinToMessageChain(groupIdOrZero, botId, this) - pptMsg?.let(::add) }.cleanupRubbishMessageElements() } diff --git a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt index d5bc1a881..f5b833bf4 100644 --- a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt @@ -26,6 +26,7 @@ import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource +import net.mamoe.mirai.utils.mapToIntArray import java.util.concurrent.atomic.AtomicBoolean internal interface MessageSourceInternal { @@ -64,44 +65,44 @@ internal suspend inline fun Message.ensureSequenceIdAvailable() { internal class MessageSourceFromFriendImpl( override val bot: Bot, - val msg: MsgComm.Msg + val msg: List ) : OnlineMessageSource.Incoming.FromFriend(), MessageSourceInternal { - override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq) + override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq } override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false) override val ids: IntArray get() = sequenceIds// msg.msgBody.richText.attr!!.random - override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random) - override val time: Int get() = msg.msgHead.msgTime + override val internalIds: IntArray get() = msg.mapToIntArray { it.msgBody.richText.attr!!.random } + override val time: Int get() = msg.first().msgHead.msgTime override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, bot.id, 0, false) } - override val sender: Friend get() = bot.getFriendOrFail(msg.msgHead.fromUin) + override val sender: Friend get() = bot.getFriendOrFail(msg.first().msgHead.fromUin) private val jceData by lazy { msg.toJceDataFriendOrTemp(internalIds) } override fun toJceData(): ImMsgBody.SourceMsg = jceData } -private fun MsgComm.Msg.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg { - val elements = msgBody.richText.elems.toMutableList().also { +private fun List.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg { + val elements = flatMap {it.msgBody.richText.elems}.toMutableList().also { if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) } return ImMsgBody.SourceMsg( - origSeqs = intArrayOf(this.msgHead.msgSeq), - senderUin = this.msgHead.fromUin, - toUin = this.msgHead.toUin, + origSeqs = mapToIntArray { it.msgHead.msgSeq }, + senderUin = first().msgHead.fromUin, + toUin = first().msgHead.toUin, flag = 1, - elems = this.msgBody.richText.elems, + elems = flatMap{it.msgBody.richText.elems}, type = 0, - time = this.msgHead.msgTime, + time = this.first().msgHead.msgTime, pbReserve = SourceMsg.ResvAttr( origUids = ids.map { it.toLong() and 0xFFFF_FFFF } ).toByteArray(SourceMsg.ResvAttr.serializer()), srcMsg = MsgComm.Msg( msgHead = MsgComm.MsgHead( - fromUin = this.msgHead.fromUin, // qq - toUin = this.msgHead.toUin, // group - msgType = this.msgHead.msgType, // 82? - c2cCmd = this.msgHead.c2cCmd, - msgSeq = this.msgHead.msgSeq, - msgTime = this.msgHead.msgTime, + fromUin = this.first().msgHead.fromUin, // qq + toUin = this.first().msgHead.toUin, // group + msgType = this.first().msgHead.msgType, // 82? + c2cCmd = this.first().msgHead.c2cCmd, + msgSeq = this.first().msgHead.msgSeq, + msgTime = this.first().msgHead.msgTime, msgUid = ids.single().toLong() and 0xFFFF_FFFF, // ok // groupInfo = MsgComm.GroupInfo(groupCode = this.msgHead.groupInfo.groupCode), isSrcMsg = true @@ -117,16 +118,24 @@ private fun MsgComm.Msg.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMs internal class MessageSourceFromTempImpl( override val bot: Bot, - private val msg: MsgComm.Msg + private val msg: List ) : OnlineMessageSource.Incoming.FromTemp(), MessageSourceInternal { - override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq) - override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random) + override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq } + override val internalIds: IntArray get() = msg.mapToIntArray{it.msgBody.richText.attr!!.random } override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false) override val ids: IntArray get() = sequenceIds// - override val time: Int get() = msg.msgHead.msgTime - override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, 0, false) } + override val time: Int get() = msg.first().msgHead.msgTime + override val originalMessage: MessageChain by lazy { + msg.toMessageChain( + bot, + bot.id, + groupIdOrZero = 0, + onlineSource = false, + isTemp = false, + ) + } override val sender: Member - get() = with(msg.msgHead) { + get() = with(msg.first().msgHead) { bot.getGroupOrFail(c2cTmpMsgHead!!.groupUin).getOrFail(fromUin) } @@ -136,24 +145,24 @@ internal class MessageSourceFromTempImpl( internal data class MessageSourceFromGroupImpl( override val bot: Bot, - private val msg: MsgComm.Msg + private val msg: List ) : OnlineMessageSource.Incoming.FromGroup(), MessageSourceInternal { override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false) - override val sequenceIds: IntArray get() = intArrayOf(msg.msgHead.msgSeq) - override val internalIds: IntArray get() = intArrayOf(msg.msgBody.richText.attr!!.random) + override val sequenceIds: IntArray get() = msg.mapToIntArray { it.msgHead.msgSeq } + override val internalIds: IntArray get() = msg.mapToIntArray{ it.msgBody.richText.attr!!.random } override val ids: IntArray get() = sequenceIds - override val time: Int get() = msg.msgHead.msgTime + override val time: Int get() = msg.first().msgHead.msgTime override val originalMessage: MessageChain by lazy { - msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = false) + msg.toMessageChain(bot, bot.id, groupIdOrZero = group.id, onlineSource = false) } override val sender: Member by lazy { (bot.getGroup( - msg.msgHead.groupInfo?.groupCode + msg.first().msgHead.groupInfo?.groupCode ?: error("cannot find groupCode for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") ) as GroupImpl).run { - get(msg.msgHead.fromUin) - ?: msg.msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }?.run { + get(msg.first().msgHead.fromUin) + ?: msg.first().msgBody.richText.elems.firstOrNull { it.anonGroupMsg != null }?.run { newAnonymous(anonGroupMsg!!.anonNick.encodeToString()) } ?: error("cannot find member for MessageSourceFromGroupImpl. msg=${msg._miraiContentToString()}") @@ -162,13 +171,13 @@ internal data class MessageSourceFromGroupImpl( override fun toJceData(): ImMsgBody.SourceMsg { return ImMsgBody.SourceMsg( - origSeqs = intArrayOf(msg.msgHead.msgSeq), - senderUin = msg.msgHead.fromUin, + origSeqs = intArrayOf(msg.first().msgHead.msgSeq), + senderUin = msg.first().msgHead.fromUin, toUin = 0, flag = 1, - elems = msg.msgBody.richText.elems, + elems = msg.flatMap { it.msgBody.richText.elems }, type = 0, - time = msg.msgHead.msgTime, + time = msg.first().msgHead.msgTime, pbReserve = EMPTY_BYTE_ARRAY, srcMsg = EMPTY_BYTE_ARRAY ) diff --git a/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt index 5352b32ee..8ad6f9e18 100644 --- a/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt @@ -25,41 +25,41 @@ import java.util.concurrent.atomic.AtomicBoolean internal class OfflineMessageSourceImplByMsg( // from other sources' originalMessage - val delegate: MsgComm.Msg, + val delegate: List, override val botId: Long, ) : OfflineMessageSource(), MessageSourceInternal { override val kind: MessageSourceKind = - if (delegate.msgHead.groupInfo != null) MessageSourceKind.GROUP else MessageSourceKind.FRIEND + if (delegate.first().msgHead.groupInfo != null) MessageSourceKind.GROUP else MessageSourceKind.FRIEND override val ids: IntArray get() = sequenceIds - override val internalIds: IntArray = intArrayOf(delegate.msgHead.msgUid.toInt()) + override val internalIds: IntArray = delegate.mapToIntArray { it.msgHead.msgUid.toInt() } override val time: Int - get() = delegate.msgHead.msgTime + get() = delegate.first().msgHead.msgTime override val fromId: Long - get() = delegate.msgHead.fromUin + get() = delegate.first().msgHead.fromUin override val targetId: Long - get() = delegate.msgHead.groupInfo?.groupCode ?: delegate.msgHead.toUin + get() = delegate.first().msgHead.groupInfo?.groupCode ?: delegate.first().msgHead.toUin override val originalMessage: MessageChain by lazy { delegate.toMessageChain( null, botId, - groupIdOrZero = delegate.msgHead.groupInfo?.groupCode ?: 0, + groupIdOrZero = delegate.first().msgHead.groupInfo?.groupCode ?: 0, onlineSource = false, - isTemp = delegate.msgHead.c2cTmpMsgHead != null + isTemp = delegate.first().msgHead.c2cTmpMsgHead != null ) } - override val sequenceIds: IntArray = intArrayOf(delegate.msgHead.msgSeq) + override val sequenceIds: IntArray = delegate.mapToIntArray { it.msgHead.msgSeq } override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false) override fun toJceData(): ImMsgBody.SourceMsg { return ImMsgBody.SourceMsg( - origSeqs = intArrayOf(delegate.msgHead.msgSeq), - senderUin = delegate.msgHead.fromUin, + origSeqs = delegate.mapToIntArray { it.msgHead.msgSeq }, + senderUin = delegate.first().msgHead.fromUin, toUin = 0, flag = 1, - elems = delegate.msgBody.richText.elems, + elems = delegate.flatMap { it.msgBody.richText.elems }, type = 0, - time = delegate.msgHead.msgTime, + time = delegate.first().msgHead.msgTime, pbReserve = EMPTY_BYTE_ARRAY, srcMsg = EMPTY_BYTE_ARRAY ) diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt index f7e8c5603..d4d8053c3 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt @@ -60,19 +60,23 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory("Onlin pbPushMsg.msg.msgHead.msgSeq ) } + val group = + bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群 + val msgs = group.groupPkgMsgParsingCache.put(pbPushMsg) + if (msgs.isEmpty()) return null var extraInfo: ImMsgBody.ExtraInfo? = null var anonymous: ImMsgBody.AnonymousGroupMsg? = null - for (elem in pbPushMsg.msg.msgBody.richText.elems) { - when { - elem.extraInfo != null -> extraInfo = elem.extraInfo - elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg + for (msg in msgs) { + for (elem in msg.msg.msgBody.richText.elems) { + when { + elem.extraInfo != null -> extraInfo = elem.extraInfo + elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg + } } } - val group = - bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群 val sender = if (anonymous != null) { group.newAnonymous(anonymous.anonNick.encodeToString()) } else { @@ -106,7 +110,7 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory("Onlin } }, sender = sender, - message = pbPushMsg.msg.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true), + message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true), permission = when { flags and 16 != 0 -> MemberPermission.ADMINISTRATOR flags and 8 != 0 -> MemberPermission.OWNER diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt index 0923ec644..03f3eee3f 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt @@ -44,6 +44,7 @@ import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket +import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.encodeToString import net.mamoe.mirai.internal.utils.io.ProtoBuf @@ -53,6 +54,7 @@ import net.mamoe.mirai.internal.utils.read import net.mamoe.mirai.internal.utils.toUHexString import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.debug +import net.mamoe.mirai.utils.mapToIntArray //0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C @@ -357,22 +359,21 @@ private object Transformers732 : Map by mapOf( val operator = if (recallReminder.uin == bot.id) group.botAsMember else group[recallReminder.uin] ?: return@lambda732 emptySequence() + val firstPkg = recallReminder.recalledMsgList.firstOrNull() ?: return@lambda732 emptySequence() - return@lambda732 recallReminder.recalledMsgList.asSequence().mapNotNull { pkg -> - when { - pkg.authorUin == bot.id && operator.id == bot.id -> null - else -> { - MessageRecallEvent.GroupRecall( - bot, - pkg.authorUin, - intArrayOf(pkg.seq), - intArrayOf(pkg.msgRandom), - pkg.time, - operator, - group - ) - } - } + return@lambda732 when { + firstPkg.authorUin == bot.id && operator.id == bot.id -> emptySequence() + else -> sequenceOf( + MessageRecallEvent.GroupRecall( + bot, + firstPkg.authorUin, + recallReminder.recalledMsgList.mapToIntArray { it.seq }, + recallReminder.recalledMsgList.mapToIntArray { it.msgRandom }, + firstPkg.time, + operator, + group + ) + ) } } ) diff --git a/mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt b/mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt new file mode 100644 index 000000000..c3d56e73b --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/utils/GroupPkgMsgParsingCache.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.internal.utils + +import kotlinx.atomicfu.locks.withLock +import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush +import net.mamoe.mirai.utils.currentTimeMillis +import java.util.concurrent.locks.ReentrantLock + +internal class GroupPkgMsgParsingCache { + class PkgMsg( + val size: Int, + val divSeq: Int, + val data: MutableMap, + ) { + val createTime = currentTimeMillis() + } + + private val deque = ArrayList(16) + private val accessLock = ReentrantLock() + private fun clearInvalid() { + deque.removeIf { + currentTimeMillis() - it.createTime > 10000L + } + } + + fun put(msg: MsgOnlinePush.PbPushMsg): List { + val head = msg.msg.contentHead ?: return listOf(msg) + val size = head.pkgNum + if (size < 2) return listOf(msg) + accessLock.withLock { + clearInvalid() + val seq = head.divSeq + val index = head.pkgIndex + val pkgMsg = deque.find { + it.divSeq == seq + } ?: PkgMsg(size, seq, mutableMapOf()).also { deque.add(it) } + pkgMsg.data[index] = msg + if (pkgMsg.data.size == pkgMsg.size) { + deque.removeIf { it.divSeq == seq } + return pkgMsg.data.entries.asSequence() + .sortedBy { it.key } + .map { it.value } + .toList() + } + return emptyList() + } + } +} \ No newline at end of file From 7796fbf2d27c2e906ca99554393142270aa8b8fb Mon Sep 17 00:00:00 2001 From: Karlatemp Date: Sun, 20 Dec 2020 15:43:08 +0800 Subject: [PATCH 15/15] Fix duplicated GroupRecall broadcast in recall group message --- mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 43f8a16bc..2ffdafdb7 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -278,16 +278,6 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { if (bot.id != source.fromId) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) } - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - MessageRecallEvent.GroupRecall( - bot, - source.fromId, - source.ids, - source.internalIds, - source.time, - null, - group - ).broadcast() network.run { PbMessageSvc.PbMsgWithDraw.createForGroupMessage(