diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bf9978fb..5ffb95444 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Version 1.x +## `1.3.0` 2020/9/16 + +### 新特性 + +- 支持群恢复相关事件: `MemberJoinEvent.Retrieve`, `BotJoinGroupEvent.Retrieve` (#531 by @Karlatemp) +- 群荣耀获取 (`Bot._lowLevelGetGroupHonorListData`) (#501 by @yyuueexxiinngg) +- 戳一戳事件: `FriendNudgedEvent`, `MemberNudgedEvent`, `BotNudgedEvent`.(#600 by @sandtechnology) +- 发送戳一戳: `Bot.nudge()`, `User.nudge()` +- 为 `BotFactory` 添加伴生对象. 在顶层方法不方便使用时可使用伴生对象的 `Bot` 构建方法 + +### 优化和修复 +- **修复好友消息和事件同步相关问题: ** + - 部分情况下无法同步好友消息 (#249) + - BotInvitedJoinGroupRequestEvent 重复执行两次 (#449) + - 群消息可能发送失败 (#527) + - 机器人启动后第二次被拉入群聊不会刷新群列表 (#580) + - 新群员入群事件只触发一次 (#590) + - 入群申请 MemberJoinRequestEvent 没有广播 (#542) + - 新成员入群未处理 (#567) +- 修复 `At` 之后的多余空格的问题 (#557) +- 修复 `QuoteReply` 前多余 `At` 和空格的问题 (#524) +- 修复群信息初始值 (`isMuteAll`, `isAllowMemberInvite`, ...) (#286) +- 修复 `Voice.url` 的域名缺失问题 (#584 by @Hieuzest) +- 修复日志颜色污染的问题 (#596 by @Karlatemp) +- 修复 `Bot.nick` 无法获取的问题 (#566) +- 修复登录时 `IndexOutOfBoundsException` 的问题 (#598) +- 修复 Bot 被踢出群收到的事件类型错误的问题 (#358) (#509 by @sandtechnology) +- 修复 `Bot.close` 后必须收到一个数据包才会关闭的问题 (#557) +- 优化 `PermissionDeniedException` 的消息内容 + ## `1.2.3` 2020/9/11 - 在同步事件失败时添加重试, 改善 #249, #482, #542, #567, #590 - 修复不断重连同一个服务器的问题 (#589) diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index fe60c3911..9c31c078b 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -9,7 +9,7 @@ object Versions { object Mirai { - const val version = "1.2.3" + const val version = "1.3.0" } object Kotlin { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotImpl.kt index b3adda8a6..da52de868 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotImpl.kt @@ -27,6 +27,7 @@ import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.network.ForceOfflineException import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.qqandroid.network.BotNetworkHandler +import net.mamoe.mirai.qqandroid.network.DefaultServerList import net.mamoe.mirai.qqandroid.network.closeAndJoin import net.mamoe.mirai.supervisorJob import net.mamoe.mirai.utils.* @@ -91,7 +92,11 @@ internal abstract class BotImpl constructor( } bot.logger.info { "Connection lost, retrying login" } - bot.asQQAndroidBot().client.serverList.removeAt(0) + bot.asQQAndroidBot().client.run { + if (serverList.isEmpty()) { + serverList.addAll(DefaultServerList) + } else serverList.removeAt(0) + } var failed = false val time = measureTime { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt index fc93ebcb7..0af5a5bd0 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt @@ -32,6 +32,7 @@ import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.event.internal.MiraiAtomicBoolean import net.mamoe.mirai.getGroupOrNull import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.* import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.qqandroid.contact.FriendImpl @@ -42,6 +43,7 @@ import net.mamoe.mirai.qqandroid.message.* import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper +import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopNum import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.LongMsg import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.* @@ -226,6 +228,30 @@ internal class QQAndroidBot constructor( accept = accept ) } + + @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER") + override suspend fun sendNudge(nudge: Nudge, receiver: Contact): Boolean { + if (configuration.protocol != BotConfiguration.MiraiProtocol.ANDROID_PHONE) { + throw UnsupportedOperationException("nudge is supported only with protocol ANDROID_PHONE") + } + + network.run { + return if (receiver is Group) { + receiver.checkIsGroupImpl() + NudgePacket.troopInvoke( + client = client, + messageReceiverGroupCode = receiver.id, + nudgeTargetId = nudge.target.id, + ).sendAndExpect().success + } else { + NudgePacket.friendInvoke( + client = client, + messageReceiverUin = receiver.id, + nudgeTargetId = nudge.target.id, + ).sendAndExpect().success + } + } + } } @@ -250,10 +276,15 @@ internal abstract class QQAndroidBotBase constructor( override val friends: ContactList = ContactList(LockFreeLinkedList()) - @JvmField internal var cachedNick: String? = null - override val nick: String get() = cachedNick ?: selfInfo.nick.also { cachedNick = it } + override lateinit var nick: String - internal lateinit var selfInfo: JceFriendInfo + internal var selfInfo: JceFriendInfo? = null + get() = field ?: error("selfInfo is not yet initialized") + set(it) { + checkNotNull(it) + field = it + nick = it.nick + } override val selfQQ: Friend by lazy { @OptIn(LowLevelAPI::class) @@ -311,12 +342,22 @@ internal abstract class QQAndroidBotBase constructor( }.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode } } + @Suppress( + "DeprecatedCallableAddReplaceWith", + "FunctionName", + "RedundantSuspendModifier", + "unused", + "unused_parameter" + ) + @Deprecated("") @OptIn(LowLevelAPI::class) - override suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo = network.run { + suspend fun _lowLevelQueryGroupInfo(groupCode: Long, stTroopNum: StTroopNum): GroupInfo = network.run { + error("This should not be invoked") + /* TroopManagement.GetGroupInfo( client = bot.client, groupCode = groupCode - ).sendAndExpect(retry = 3) + ).sendAndExpect(retry = 3).also { it.stTroopNum = stTroopNum }*/ } @OptIn(LowLevelAPI::class) @@ -608,6 +649,30 @@ internal abstract class QQAndroidBotBase constructor( return json.decodeFromString(GroupActiveData.serializer(), rep) } + @LowLevelAPI + @MiraiExperimentalAPI + override suspend fun _lowLevelGetGroupHonorListData(groupId: Long, type: GroupHonorType): GroupHonorListData? { + val data = network.async { + MiraiPlatformUtils.Http.get { + url("https://qun.qq.com/interactive/honorlist") + parameter("gc", groupId) + parameter("type", type.value) + headers { + append( + "cookie", + "uin=o${id};" + + " skey=${client.wLoginSigInfo.sKey.data.encodeToString()};" + + " p_uin=o${id};" + + " p_skey=${client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString()}; " + ) + } + } + } + val rep = data.await() + val jsonText = Regex("""window.__INITIAL_STATE__=(.+?)""").find(rep)?.groupValues?.get(1) + return jsonText?.let { json.decodeFromString(GroupHonorListData.serializer(), it) } + } + @JvmSynthetic @LowLevelAPI @MiraiExperimentalAPI diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt index 6254b246c..613ce5b91 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt @@ -124,6 +124,9 @@ internal class MemberImpl constructor( @Suppress("PropertyName") var _muteTimestamp: Int = memberInfo.muteTimestamp + @Suppress("PropertyName") + var _nudgeTimestamp: Long = 0L + override val muteTimeRemaining: Int get() = if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) { 0 @@ -197,7 +200,7 @@ internal class MemberImpl constructor( private fun checkBotPermissionHigherThanThis(operationName: String) { check(group.botPermission > this.permission) { throw PermissionDeniedException( - "`$operationName` operation requires a higher permission, while" + + "`$operationName` operation requires a higher permission, while " + "${group.botPermission} < ${this.permission}" ) } @@ -219,7 +222,6 @@ internal class MemberImpl constructor( net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast() } - @JvmSynthetic override suspend fun kick(message: String) { checkBotPermissionHigherThanThis("kick") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/conversions.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/conversions.kt index 08583d8af..9d96f1617 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/conversions.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/conversions.kt @@ -113,7 +113,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B } is At -> { elements.add(ImMsgBody.Elem(text = it.toJceData())) - elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) + // elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) + // removed by https://github.com/mamoe/mirai/issues/524 + // 发送 QuoteReply 消息时无可避免的产生多余空格 #524 } is PokeMessage -> { elements.add( @@ -151,7 +153,9 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean, withGeneralFlags: B when (val source = it.source) { is OnlineMessageSource.Incoming.FromGroup -> { transformOneMessage(At(source.sender)) - transformOneMessage(PlainText(" ")) + // transformOneMessage(PlainText(" ")) + // removed by https://github.com/mamoe/mirai/issues/524 + // 发送 QuoteReply 消息时无可避免的产生多余空格 #524 } } } @@ -297,6 +301,17 @@ private fun MessageChain.cleanupRubbishMessageElements(): MessageChain { return@forEach } } + + + if (element is QuoteReply) { + // 客户端为兼容早期不支持 QuoteReply 的客户端而添加的 At + removeLastOrNull()?.let { rm -> + if ((rm as? PlainText)?.content != " ") add(rm) + else removeLastOrNull()?.let { rm2 -> + if (rm2 !is At) add(rm2) + } + } + } add(element) last = element } @@ -316,7 +331,7 @@ internal val MIRAI_CUSTOM_ELEM_TYPE = "mirai".hashCode() // 103904510 @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") internal fun List.joinToMessageChain(groupIdOrZero: Long, bot: Bot, list: MessageChainBuilder) { - // (this._miraiContentToString()) + // (this._miraiContentToString().soutv()) this.forEach { element -> when { element.srcMsg != null -> { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 44bb1f3d3..46263e461 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -246,7 +246,6 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo // self info data.selfInfo?.run { - bot.cachedNick = null bot.selfInfo = this // bot.remark = remark ?: "" // bot.sex = sex @@ -272,28 +271,11 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo suspend fun StTroopNum.reloadGroup() { retryCatching(3) { bot.groups.delegate.addLast( - @Suppress("DuplicatedCode") GroupImpl( bot = bot, coroutineContext = bot.coroutineContext, id = groupCode, - groupInfo = bot._lowLevelQueryGroupInfo(groupCode).apply { - this as GroupInfoImpl - - if (this.delegate.groupName == null) { - this.delegate.groupName = groupName - } - - if (this.delegate.groupMemo == null) { - this.delegate.groupMemo = groupMemo - } - - if (this.delegate.groupUin == null) { - this.delegate.groupUin = groupUin - } - - this.delegate.groupCode = this@reloadGroup.groupCode - }, + groupInfo = GroupInfoImpl(this), members = bot._lowLevelQueryGroupMemberList( groupUin, groupCode, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index 129372531..4d53d7606 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.NoServerAvailableException import net.mamoe.mirai.qqandroid.BotAccount import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.network.protocol.SyncingCacheList import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FileStoragePushFSSvcListFuckKotlin import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger @@ -191,9 +192,6 @@ internal open class QQAndroidClient( private val highwayDataTransSequenceIdForApplyUp: AtomicInt = atomic(77918) internal fun nextHighwayDataTransSequenceIdForApplyUp(): Int = highwayDataTransSequenceIdForApplyUp.getAndAdd(2) - internal val onlinePushCacheList: AtomicResizeCacheList = AtomicResizeCacheList(20.secondsToMillis) - internal val pbPushTransMsgCacheList: AtomicResizeCacheList = AtomicResizeCacheList(20.secondsToMillis) - val appClientVersion: Int = 0 var networkType: NetworkType = NetworkType.WIFI @@ -205,16 +203,49 @@ internal open class QQAndroidClient( */ val protocolVersion: Short = 8001 - class C2cMessageSyncData { + class MessageSvcSyncData { val firstNotify: AtomicBoolean = atomic(true) @Volatile var syncCookie: ByteArray? = null var pubAccountCookie = EMPTY_BYTE_ARRAY var msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY + + + internal data class PbGetMessageSyncId( + val uid: Long, + val sequence: Int, + val time: Int + ) + + val pbGetMessageCacheList = SyncingCacheList() + + internal data class SystemMsgNewGroupSyncId( + val sequence: Long, + val time: Long + ) + + val systemMsgNewGroupCacheList = SyncingCacheList(10) + + + internal data class PbPushTransMsgSyncId( + val uid: Long, + val sequence: Int, + val time: Int + ) + + val pbPushTransMsgCacheList = SyncingCacheList(10) + + internal data class OnlinePushReqPushSyncId( + val uid: Long, + val sequence: Short, + val time: Long + ) + + val onlinePushReqPushCacheList = SyncingCacheList(50) } - val c2cMessageSync = C2cMessageSyncData() + val syncingController = MessageSvcSyncData() /* * 以下登录使用 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/SyncingCacheList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/SyncingCacheList.kt new file mode 100644 index 000000000..70da8a59f --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/SyncingCacheList.kt @@ -0,0 +1,27 @@ +/* + * + * * Copyright 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.qqandroid.network.protocol + +import net.mamoe.mirai.qqandroid.utils.LinkedList +import kotlin.jvm.Synchronized + +internal class SyncingCacheList(private val size: Int = 50) { + private val packetIdList = LinkedList() + + @Synchronized // faster than suspending Mutex + fun addCache(element: E): Boolean { + if (packetIdList.contains(element)) return false // duplicate + packetIdList.addLast(element) + if (packetIdList.size >= size) packetIdList.removeFirst() + return true + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt index 0bb154612..ad51169fb 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt @@ -172,13 +172,13 @@ internal class Oidb0x88d : ProtoBuf { @ProtoNumber(12) @JvmField val groupDefaultPage: Int? = null, @ProtoNumber(13) @JvmField val groupInfoSeq: Int? = null, @ProtoNumber(14) @JvmField val groupRoamingTime: Int? = null, - @ProtoNumber(15) var groupName: String? = null, - @ProtoNumber(16) var groupMemo: String? = null, + @ProtoNumber(15) @JvmField val groupName: String? = null, + @ProtoNumber(16) @JvmField val groupMemo: String? = null, @ProtoNumber(17) @JvmField val ingGroupFingerMemo: String? = null, @ProtoNumber(18) @JvmField val ingGroupClassText: String? = null, @ProtoNumber(19) @JvmField val groupAllianceCode: List? = null, @ProtoNumber(20) @JvmField val groupExtraAdmNum: Int? = null, - @ProtoNumber(21) var groupUin: Long? = null, + @ProtoNumber(21) @JvmField val groupUin: Long? = null, @ProtoNumber(22) @JvmField val groupCurMsgSeq: Int? = null, @ProtoNumber(23) @JvmField val groupLastMsgTime: Int? = null, @ProtoNumber(24) @JvmField val ingGroupQuestion: String? = null, @@ -258,7 +258,6 @@ internal class Oidb0x88d : ProtoBuf { @ProtoNumber(98) @JvmField val cmduinRingtoneId: Int? = null, @ProtoNumber(99) @JvmField val groupFlagext4: Int? = null, @ProtoNumber(100) @JvmField val groupFreezeReason: Int? = null, - @ProtoNumber(101) var groupCode: Long? = null // mirai 添加 ) : ProtoBuf @Serializable @@ -2423,3 +2422,17 @@ internal class Cmd0x6ce : ProtoBuf { ) : ProtoBuf } +@Serializable +internal class Cmd0xed3 : ProtoBuf { + @Serializable + internal class RspBody : ProtoBuf + + @Serializable + internal class ReqBody( + @ProtoNumber(1) @JvmField val toUin: Long = 0L, + @ProtoNumber(2) @JvmField val groupCode: Long = 0L, + @ProtoNumber(3) @JvmField val msgSeq: Int = 0, + @ProtoNumber(4) @JvmField val msgRandom: Int = 0, + @ProtoNumber(5) @JvmField val aioUin: Long = 0L + ) : ProtoBuf +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index bff03ef55..25333a7d0 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -13,10 +13,7 @@ import kotlinx.io.core.* import net.mamoe.mirai.event.Event import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.Packet -import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.MultiMsg -import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.NewContact -import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc -import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.* import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image.LongConn import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.* @@ -148,9 +145,10 @@ internal object KnownPacketFactories { TroopManagement.EditSpecialTitle, TroopManagement.Mute, TroopManagement.GroupOperation, - TroopManagement.GetGroupInfo, + // TroopManagement.GetGroupInfo, TroopManagement.EditGroupNametag, TroopManagement.Kick, + NudgePacket, Heartbeat.Alive, PbMessageSvc.PbMsgWithDraw, MultiMsg.ApplyUp, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt index e415c4c1e..4cb016810 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt @@ -58,7 +58,7 @@ internal class NewContact { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): NewFriendRequestEvent? { readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run { - val struct = friendmsgs?.firstOrNull() + val struct = friendmsgs?.firstOrNull()// 会有重复且无法过滤, 不要用 map return struct?.msg?.run { NewFriendRequestEvent( bot, @@ -145,9 +145,16 @@ internal class NewContact { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet? { readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run { - val struct = groupmsgs?.firstOrNull() + val struct = groupmsgs?.firstOrNull() ?: return null // 会有重复且无法过滤, 不要用 map - return struct?.msg?.run { + if (!bot.client.syncingController.systemMsgNewGroupCacheList.addCache( + QQAndroidClient.MessageSvcSyncData.SystemMsgNewGroupSyncId(struct.msgSeq, struct.msgTime) + ) + ) { // duplicate + return null + } + + return struct.msg?.run { //this.soutv("SystemMsg") when (subType) { 1 -> { // 处理被邀请入群 或 处理成员入群申请 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NudgePacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NudgePacket.kt new file mode 100644 index 000000000..a4c602a7a --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NudgePacket.kt @@ -0,0 +1,79 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.network.protocol.packet.chat + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.readBytes +import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.network.Packet +import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0xed3 +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OidbSso +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs +import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray +import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf + +internal object NudgePacket : OutgoingPacketFactory("OidbSvc.0xed3") { + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + with(readBytes().loadAs(OidbSso.OIDBSSOPkg.serializer())) { + return Response(result == 0, result) + } + } + + class Response(val success: Boolean, val code: Int) : Packet { + override fun toString(): String = "NudgeResponse(success=$success,code=$code)" + } + + fun friendInvoke( + client: QQAndroidClient, + nudgeTargetId: Long, + messageReceiverUin: Long, + ): OutgoingPacket { + return buildOutgoingUniPacket(client) { + writeProtoBuf( + OidbSso.OIDBSSOPkg.serializer(), + OidbSso.OIDBSSOPkg( + command = 3795, + serviceType = 1, + result = 0, + bodybuffer = Cmd0xed3.ReqBody( + toUin = nudgeTargetId, + aioUin = messageReceiverUin + ).toByteArray(Cmd0xed3.ReqBody.serializer()) + ) + ) + } + } + + fun troopInvoke( + client: QQAndroidClient, + messageReceiverGroupCode: Long, + nudgeTargetId: Long, + ): OutgoingPacket { + return buildOutgoingUniPacket(client) { + writeProtoBuf( + OidbSso.OIDBSSOPkg.serializer(), + OidbSso.OIDBSSOPkg( + command = 3795, + serviceType = 1, + result = 0, + bodybuffer = Cmd0xed3.ReqBody( + toUin = nudgeTargetId, + groupCode = messageReceiverGroupCode + ).toByteArray(Cmd0xed3.ReqBody.serializer()) + ) + ) + } + } + +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt index e1565c5f2..c2d75eaf2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt @@ -14,13 +14,13 @@ import kotlinx.io.core.buildPacket import kotlinx.io.core.readBytes import kotlinx.io.core.toByteArray import net.mamoe.mirai.LowLevelAPI -import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.jce.ModifyGroupCardReq import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket +import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopNum import net.mamoe.mirai.qqandroid.network.protocol.data.jce.stUinInfo import net.mamoe.mirai.qqandroid.network.protocol.data.proto.* import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket @@ -32,19 +32,19 @@ import net.mamoe.mirai.data.GroupInfo as MiraiGroupInfo @OptIn(LowLevelAPI::class) internal class GroupInfoImpl( - internal val delegate: Oidb0x88d.GroupInfo + private val stTroopNum: StTroopNum ) : MiraiGroupInfo, Packet, Packet.NoLog { - override val uin: Long get() = delegate.groupUin ?: error("cannot find groupUin") - override val owner: Long get() = delegate.groupOwner ?: error("cannot find groupOwner") - override val groupCode: Long get() = Group.calculateGroupCodeByGroupUin(uin) - override val memo: String get() = delegate.groupMemo ?: error("cannot find groupMemo") - override val name: String get() = delegate.groupName ?: delegate.longGroupName ?: error("cannot find groupName") - override val allowMemberInvite get() = delegate.groupFlagExt?.and(0x000000c0) != 0 - override val allowAnonymousChat get() = delegate.groupFlagExt?.and(0x40000000) == 0 - override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0 - override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0 - override val muteAll: Boolean get() = delegate.shutupTimestamp != 0 - override val botMuteTimestamp: Int get() = delegate.shutupTimestampMe ?: 0 + override val uin: Long get() = stTroopNum.groupUin + override val owner: Long get() = stTroopNum.dwGroupOwnerUin + override val groupCode: Long get() = stTroopNum.groupCode + override val memo: String get() = stTroopNum.groupMemo + override val name: String get() = stTroopNum.groupName + override val allowMemberInvite get() = stTroopNum.dwGroupFlagExt?.and(0x000000c0) != 0L + override val allowAnonymousChat get() = stTroopNum.dwGroupFlagExt?.and(0x40000000) == 0L + override val autoApprove get() = stTroopNum.dwGroupFlagExt3?.and(0x00100000) == 0L + override val confessTalk get() = stTroopNum.dwGroupFlagExt3?.and(0x00002000) == 0L + override val muteAll: Boolean get() = stTroopNum.dwShutUpTimestamp != 0L + override val botMuteTimestamp: Int get() = stTroopNum.dwMyShutUpTimestamp?.toInt() ?: 0 } internal class TroopManagement { @@ -88,6 +88,7 @@ internal class TroopManagement { internal object GetGroupInfo : OutgoingPacketFactory("OidbSvc.0x88d_7") { + @Deprecated("") operator fun invoke( client: QQAndroidClient, groupCode: Long @@ -107,8 +108,8 @@ internal class TroopManagement { groupFlagExt = 0, groupFlagext4 = 0, groupFlag = 0, - groupFlagext3 = 0,//获取confess - noFingerOpenFlag = 0, + groupFlagext3 = 1,//获取confess + noFingerOpenFlag = 1, cmduinFlagEx2 = 0, groupTypeFlag = 0, appPrivilegeFlag = 0, @@ -135,12 +136,14 @@ internal class TroopManagement { } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupInfoImpl { + error("deprecated") + /* with( this.readBytes() .loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!! ) { - return GroupInfoImpl(this) - } + return GroupInfoImpl() + }*/ } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt index 71e9cbbaa..816949169 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt @@ -17,7 +17,6 @@ import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch -import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact @@ -63,10 +62,6 @@ import kotlin.random.Random internal object MessageSvcPbGetMsg : OutgoingPacketFactory("MessageSvc.PbGetMsg") { - private val msgUidQueue = ArrayDeque() - private val msgUidSet = hashSetOf() - private val msgQueueMutex = Mutex() - @Suppress("SpellCheckingInspection") operator fun invoke( client: QQAndroidClient, @@ -88,7 +83,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory { - bot.client.c2cMessageSync.syncCookie = resp.syncCookie - bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie + bot.client.syncingController.syncCookie = resp.syncCookie + bot.client.syncingController.pubAccountCookie = resp.pubAccountCookie } 1 -> { - bot.client.c2cMessageSync.syncCookie = resp.syncCookie + bot.client.syncingController.syncCookie = resp.syncCookie } 2 -> { - bot.client.c2cMessageSync.pubAccountCookie = resp.pubAccountCookie + bot.client.syncingController.pubAccountCookie = resp.pubAccountCookie } } @@ -170,7 +176,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory { msg -> - - msgQueueMutex.lock() - val msgUid = msg.msgHead.msgUid - if (msgUidSet.size > 50) { - msgUidSet.remove(msgUidQueue.removeFirst()) - } - if (!msgUidSet.add(msgUid)) { - msgQueueMutex.unlock() - return@mapNotNull null - } - msgQueueMutex.unlock() - msgUidQueue.addLast(msgUid) - - suspend fun createGroupForBot(groupUin: Long): Group? { - val group = bot.getGroupByUinOrNull(groupUin) - if (group != null) { - return null - } - - return bot.getNewGroup(Group.calculateGroupCodeByGroupUin(groupUin))?.apply { - bot.groups.delegate.addLast(this) - } - } + .mapNotNull { msg -> + if (!bot.client.syncingController.pbGetMessageCacheList.addCache( + QQAndroidClient.MessageSvcSyncData.PbGetMessageSyncId( + uid = msg.msgHead.msgUid, + sequence = msg.msgHead.msgSeq, + time = msg.msgHead.msgTime + ) + ) + ) return@mapNotNull null when (msg.msgHead.msgType) { 33 -> bot.groupListModifyLock.withLock { if (msg.msgHead.authUin == bot.id) { // 邀请入群 - return@mapNotNull createGroupForBot(msg.msgHead.fromUin)?.let { + return@mapNotNull bot.createGroupForBot(msg.msgHead.fromUin)?.let { // package: 27 0B 60 E7 01 CA CC 69 8B 83 44 71 47 90 06 B9 DC C0 ED D4 B1 00 30 33 44 30 42 38 46 30 39 37 32 38 35 43 34 31 38 30 33 36 41 34 36 31 36 31 35 32 37 38 46 46 43 30 41 38 30 36 30 36 45 38 31 43 39 41 34 38 37 // package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey val invitorUin = msg.msgBody.msgContent.sliceArray(10..13).toInt().toLong() @@ -256,7 +247,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory bot.groupListModifyLock.withLock { // 建群 - return@mapNotNull createGroupForBot(msg.msgHead.fromUin) + return@mapNotNull bot.createGroupForBot(msg.msgHead.fromUin) ?.let { BotJoinGroupEvent.Active(it) } } @@ -265,7 +256,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory() } return @@ -438,7 +429,7 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory() } return @@ -454,28 +445,11 @@ internal suspend fun QQAndroidBot.getNewGroup(groupCode: Long): Group? { .sendAndExpect(timeoutMillis = 10_000, retry = 5) }.groups.firstOrNull { it.groupCode == groupCode } ?: return null - @Suppress("DuplicatedCode") return GroupImpl( bot = this, coroutineContext = coroutineContext, id = groupCode, - groupInfo = _lowLevelQueryGroupInfo(troopNum.groupCode).apply { - this as GroupInfoImpl - - if (this.delegate.groupName == null) { - this.delegate.groupName = troopNum.groupName - } - - if (this.delegate.groupMemo == null) { - this.delegate.groupMemo = troopNum.groupMemo - } - - if (this.delegate.groupUin == null) { - this.delegate.groupUin = troopNum.groupUin - } - - this.delegate.groupCode = troopNum.groupCode - }, + groupInfo = GroupInfoImpl(troopNum), members = _lowLevelQueryGroupMemberList( troopNum.groupUin, troopNum.groupCode, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt index 414d296ec..0dda51956 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt @@ -80,7 +80,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory( override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket? { - client.c2cMessageSync.firstNotify.loop { firstNotify -> + client.syncingController.firstNotify.loop { firstNotify -> network.run { return MessageSvcPbGetMsg( client, MsgSvc.SyncFlag.START, if (firstNotify) { - if (!client.c2cMessageSync.firstNotify.compareAndSet(firstNotify, false)) { + if (!client.syncingController.firstNotify.compareAndSet(firstNotify, false)) { return@loop } null diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt index d4d4f33c4..ffc1de153 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt @@ -20,15 +20,16 @@ import kotlinx.io.core.readUByte import kotlinx.io.core.readUInt import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.contact.MemberPermission -import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent -import net.mamoe.mirai.event.events.MemberLeaveEvent -import net.mamoe.mirai.event.events.MemberPermissionChangeEvent +import net.mamoe.mirai.data.MemberInfo +import net.mamoe.mirai.event.events.* import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.contact.GroupImpl import net.mamoe.mirai.qqandroid.contact.MemberImpl import net.mamoe.mirai.qqandroid.contact.checkIsMemberImpl import net.mamoe.mirai.qqandroid.message.contextualBugReportException +import net.mamoe.mirai.qqandroid.network.MultiPacketByIterable import net.mamoe.mirai.qqandroid.network.Packet +import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket @@ -45,52 +46,156 @@ internal object OnlinePushPbPushTransMsg : override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer()) - - if (!bot.client.pbPushTransMsgCacheList.ensureNoDuplication(content.msgSeq)) { + if (!bot.client.syncingController.pbPushTransMsgCacheList.addCache( + content.run { + QQAndroidClient.MessageSvcSyncData.PbPushTransMsgSyncId(msgUid, msgSeq, msgTime) + } + ) + ) { return null } + // bot.network.logger.debug { content._miraiContentToString() } + content.msgData.read { when (content.msgType) { 44 -> { + // 3D C4 33 DD 01 FF CD 76 F4 03 C3 7E 2E 34 + // 群转让 + // start with 3D C4 33 DD 01 FF + // 3D C4 33 DD 01 FF C3 7E 2E 34 CD 76 F4 03 + // 权限变更 + // 3D C4 33 DD 01 00/01 ..... + // 3D C4 33 DD 01 01 C3 7E 2E 34 01 this.discardExact(5) - val var4 = readByte().toInt() - var var5 = 0L - val target = readUInt().toLong() - if (var4 != 0 && var4 != 1) { - var5 = readUInt().toLong() - } - - val group = bot.getGroupByUin(content.fromUin) as GroupImpl - - if (var5 == 0L && this.remaining == 1L) {//管理员变更 - val newPermission = - if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR - else MemberPermission.MEMBER - - if (target == bot.id) { - if (group.botPermission == newPermission) { - return null + when (val mode = readUByte().toInt()) { + 0xFF -> { + // 群转让 / huifu.qq.com + // From -> to + val from = readUInt().toLong() + val to = readUInt().toLong() + val results = ArrayList() + // println("$from -> $to") + if (to == bot.id) { + if (bot.getGroupByUinOrNull(content.fromUin) == null) { + MessageSvcPbGetMsg.run { + results.add( + BotJoinGroupEvent.Retrieve( + bot.createGroupForBot(content.fromUin)!! + ) + ) + } + } + } + val group = bot.getGroupByUin(content.fromUin) as GroupImpl + if (from == bot.id) { + if (group.botPermission != MemberPermission.MEMBER) + results.add( + BotGroupPermissionChangeEvent( + group, group.botPermission.also { + group.botAsMember.checkIsMemberImpl().permission = + MemberPermission.MEMBER + }, + MemberPermission.MEMBER + ) + ) + } else { + val member = group[from] as MemberImpl + if (member.permission != MemberPermission.MEMBER) { + results.add( + MemberPermissionChangeEvent( + member, + member.permission.also { member.permission = MemberPermission.MEMBER }, + MemberPermission.MEMBER + ) + ) + } + } + if (to == bot.id) { + if (group.botPermission != MemberPermission.OWNER) { + results.add( + BotGroupPermissionChangeEvent( + group, + group.botAsMember.permission.also { + group.botAsMember.checkIsMemberImpl().permission = + MemberPermission.OWNER + }, + MemberPermission.OWNER + ) + ) + } + } else { + val newOwner = group.getOrNull(to) ?: group.newMember(object : MemberInfo { + override val nameCard: String + get() = "" + override val permission: MemberPermission + get() = MemberPermission.OWNER + override val specialTitle: String + get() = "" + override val muteTimestamp: Int + get() = 0 + override val uin: Long + get() = to + override val nick: String + get() = "" + }).also { owner -> + owner.checkIsMemberImpl().permission = MemberPermission.OWNER + group.members.delegate.addLast(owner) + results.add(MemberJoinEvent.Retrieve(owner)) + } + if (newOwner.permission != MemberPermission.OWNER) { + results.add( + MemberPermissionChangeEvent( + newOwner, + newOwner.permission.also { + newOwner.checkIsMemberImpl().permission = MemberPermission.OWNER + }, + MemberPermission.OWNER + ) + ) + } + } + return MultiPacketByIterable(results) + } + else -> { + var var5 = 0L + val target = readUInt().toLong() + if (mode != 0 && mode != 1) { + var5 = readUInt().toLong() } - return BotGroupPermissionChangeEvent( - group, - group.botPermission.also { - group.botAsMember.checkIsMemberImpl().permission = newPermission - }, - newPermission - ) - } else { - val member = group[target] as MemberImpl - if (member.permission == newPermission) { - return null - } + val group = bot.getGroupByUin(content.fromUin) as GroupImpl - return MemberPermissionChangeEvent( - member, - member.permission.also { member.permission = newPermission }, - newPermission - ) + if (var5 == 0L && this.remaining == 1L) {//管理员变更 + val newPermission = + if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR + else MemberPermission.MEMBER + + if (target == bot.id) { + if (group.botPermission == newPermission) { + return null + } + + return BotGroupPermissionChangeEvent( + group, + group.botPermission.also { + group.botAsMember.checkIsMemberImpl().permission = newPermission + }, + newPermission + ) + } else { + val member = group[target] as MemberImpl + if (member.permission == newPermission) { + return null + } + + return MemberPermissionChangeEvent( + member, + member.permission.also { member.permission = newPermission }, + newPermission + ) + } + } } } } @@ -108,28 +213,41 @@ internal object OnlinePushPbPushTransMsg : A8 32 51 A1 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36 */ - readUInt().toLong() // group, uin or code ? - - discardExact(1) + readUInt().toLong() // groupUin + readByte().toInt() // follow type val target = readUInt().toLong() val type = readUByte().toInt() val operator = readUInt().toLong() val groupUin = content.fromUin when (type) { - 0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group -> - val member = group.getOrNull(target) as? MemberImpl ?: return null - return MemberLeaveEvent.Quit(member.also { - member.cancel(CancellationException("Leaved actively")) - group.members.delegate.remove(member) - }) + 2, 0x82 -> bot.getGroupByUinOrNull(groupUin)?.let { group -> + if (target == bot.id) { + return BotLeaveEvent.Active(group).also { + group.cancel(CancellationException("Leaved actively")) + bot.groups.delegate.remove(group) + } + } else { + val member = group.getOrNull(target) as? MemberImpl ?: return null + return MemberLeaveEvent.Quit(member.also { + member.cancel(CancellationException("Leaved actively")) + group.members.delegate.remove(member) + }) + } } - 0x83 -> bot.getGroupByUin(groupUin).let { group -> - val member = group.getOrNull(target) as? MemberImpl ?: return null - return MemberLeaveEvent.Kick(member.also { - member.cancel(CancellationException("Leaved actively")) - group.members.delegate.remove(member) - }, group.members[operator]) + 3, 0x83 -> bot.getGroupByUin(groupUin).let { group -> + if (target == bot.id) { + return BotLeaveEvent.Kick(group.members[operator]).also { + group.cancel(CancellationException("Being kicked")) + bot.groups.delegate.remove(group) + } + } else { + val member = group.getOrNull(target) as? MemberImpl ?: return null + return MemberLeaveEvent.Kick(member.also { + member.cancel(CancellationException("Being kicked")) + group.members.delegate.remove(member) + }, group.members[operator]) + } } } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt index f84a8ab08..143e7a56d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt @@ -14,8 +14,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive -import kotlinx.coroutines.CancellationException -import kotlinx.coroutines.cancel import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes @@ -23,6 +21,8 @@ import kotlinx.io.core.readUInt import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.JavaFriendlyAPI +import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.Member import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.event.events.* import net.mamoe.mirai.getFriendOrNull @@ -37,8 +37,9 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.OnlinePushPack import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x115 +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x122 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.* -import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x44 +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0x44.Submsgtype0x44 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Submsgtype0xb3 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory @@ -66,7 +67,11 @@ internal object OnlinePushReqPush : IncomingPacketFactory Sequence ): Sequence { return asSequence().filter { msg -> - client.onlinePushCacheList.ensureNoDuplication(msg.shMsgSeq) + !client.syncingController.onlinePushReqPushCacheList.addCache( + QQAndroidClient.MessageSvcSyncData.OnlinePushReqPushSyncId( + uid = msg.lMsgUid ?: 0, sequence = msg.shMsgSeq, time = msg.uMsgTime + ) + ) }.flatMap { it.vMsg.read { mapper(it) } } } @@ -101,7 +106,7 @@ internal object OnlinePushReqPush : IncomingPacketFactory { val notifyMsgBody = readJceStruct(MsgType0x210.serializer()) Transformers528[notifyMsgBody.uSubMsgType] - ?.let { processor -> processor(notifyMsgBody, bot) } + ?.let { processor -> processor(notifyMsgBody, bot, msgInfo) } ?: kotlin.run { bot.network.logger.debug { "unknown group 528 type 0x${notifyMsgBody.uSubMsgType.toUHexString("")}, data: " + notifyMsgBody.vProtobuf.toUHexString() @@ -231,6 +236,44 @@ private object Transformers732 : Map by mapOf( return@lambda732 sequenceOf(GroupAllowAnonymousChatEvent(!new, new, group, operator)) }, + //系统提示 + 0x14 to lambda732 { group: GroupImpl, bot: QQAndroidBot -> + + discardExact(1) + val grayTip = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()).optGeneralGrayTip + when (grayTip?.templId) { + //戳一戳 + 10043L, 1134L, 1135L -> { + //预置数据,服务器将不会提供己方已知消息 + var action = "" + var from: Member = group.botAsMember + var target: Member = group.botAsMember + var suffix = "" + grayTip.msgTemplParam?.map { + Pair(it.name.decodeToString(), it.value.decodeToString()) + }?.asSequence()?.forEach { (key, value) -> + run { + when (key) { + "action_str" -> action = value + "uin_str1" -> from = group[value.toLong()] + "uin_str2" -> target = group[value.toLong()] + "suffix_str" -> suffix = value + } + } + } + if (target.id == bot.id) { + return@lambda732 sequenceOf(BotNudgedEvent(from, action, suffix)) + } + return@lambda732 sequenceOf(MemberNudgedEvent(from, target, action, suffix)) + } + else -> { + bot.network.logger.debug { + "Unknown Transformers528 0x14 template\ntemplId=${grayTip?.templId}\nPermList=${grayTip?.msgTemplParam?._miraiContentToString()}" + } + return@lambda732 emptySequence() + } + } + }, // 传字符串信息 0x10 to lambda732 { group: GroupImpl, bot: QQAndroidBot -> val dataBytes = readBytes(26) @@ -329,17 +372,28 @@ private object Transformers732 : Map by mapOf( } ) -internal val ignoredLambda528: Lambda528 = lambda528 { emptySequence() } +internal val ignoredLambda528: Lambda528 = lambda528 { _, _ -> emptySequence() } internal interface Lambda528 { - operator fun invoke(msg: MsgType0x210, bot: QQAndroidBot): Sequence + operator fun invoke(msg: MsgType0x210, bot: QQAndroidBot, msgInfo: MsgInfo): Sequence } +@kotlin.internal.LowPriorityInOverloadResolution internal inline fun lambda528(crossinline block: MsgType0x210.(QQAndroidBot) -> Sequence): Lambda528 { return object : Lambda528 { - override fun invoke(msg: MsgType0x210, bot: QQAndroidBot): Sequence { + override fun invoke(msg: MsgType0x210, bot: QQAndroidBot, msgInfo: MsgInfo): Sequence { return block(msg, bot) } + + } +} + +internal inline fun lambda528(crossinline block: MsgType0x210.(QQAndroidBot, MsgInfo) -> Sequence): Lambda528 { + return object : Lambda528 { + override fun invoke(msg: MsgType0x210, bot: QQAndroidBot, msgInfo: MsgInfo): Sequence { + return block(msg, bot, msgInfo) + } + } } @@ -353,7 +407,7 @@ internal object Transformers528 : Map by mapOf( 0x8AL to lambda528 { bot -> @Serializable - data class Sub8AMsgInfo( + class Sub8AMsgInfo( @ProtoNumber(1) val fromUin: Long, @ProtoNumber(2) val botUin: Long, @ProtoNumber(3) val srcId: Int, @@ -367,7 +421,7 @@ internal object Transformers528 : Map by mapOf( ) : ProtoBuf @Serializable - data class Sub8A( + class Sub8A( @ProtoNumber(1) val msgInfo: List, @ProtoNumber(2) val appId: Int, // 1 @ProtoNumber(3) val instId: Int, // 1 @@ -403,7 +457,7 @@ internal object Transformers528 : Map by mapOf( bot.friends.delegate.addLast(new) return@lambda528 sequenceOf(FriendAddEvent(new)) }, - 0xE2L to lambda528 { + 0xE2L to lambda528 { _ -> // TODO: unknown. maybe messages. // 0A 35 08 00 10 A2 FF 8C F0 03 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B 28 01 // vProtobuf.loadAs(Msgtype0x210.serializer()) @@ -411,7 +465,7 @@ internal object Transformers528 : Map by mapOf( return@lambda528 emptySequence() }, 0x44L to lambda528 { bot -> - val msg = vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer()) + val msg = vProtobuf.loadAs(Submsgtype0x44.MsgBody.serializer()) when { msg.msgCleanCountMsg != null -> { @@ -426,20 +480,55 @@ internal object Transformers528 : Map by mapOf( return@lambda528 emptySequence() }, // bot 在其他客户端被踢或主动退出而同步情况 - 0xD4L to lambda528 { bot -> + 0xD4L to lambda528 { _ -> // this.soutv("0x210") - @Serializable - data class SubD4( - // ok - val uin: Long - ) : ProtoBuf + /* @Serializable + data class SubD4( + // ok + val uin: Long + ) : ProtoBuf - val uin = vProtobuf.loadAs(SubD4.serializer()).uin - val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin) - return@lambda528 if (group != null && bot.groups.delegate.remove(group)) { - group.cancel(CancellationException("Being kicked")) - sequenceOf(BotLeaveEvent.Active(group)) - } else emptySequence() + val uin = vProtobuf.loadAs(SubD4.serializer()).uin + val group = bot.getGroupByUinOrNull(uin) ?: bot.getGroupOrNull(uin) + return@lambda528 if (group != null && bot.groups.delegate.remove(group)) { + group.cancel(CancellationException("Being kicked")) + sequenceOf(BotLeaveEvent.Active(group)) + } else emptySequence()*/ + + //ignore + return@lambda528 emptySequence() + }, + //戳一戳信息等 + 0x122L to lambda528 { bot, _ -> + val body = vProtobuf.loadAs(Submsgtype0x122.Submsgtype0x122.MsgBody.serializer()) + when (body.templId) { + //戳一戳 + 1134L, 1135L, 1136L, 10043L -> { + //预置数据,服务器将不会提供己方已知消息 + var from: Friend = bot.selfQQ + var action = "" + var target: Friend = bot.selfQQ + var suffix = "" + body.msgTemplParam?.asSequence()?.map { + it.name.decodeToString() to it.value.decodeToString() + }?.forEach { (key, value) -> + when (key) { + "action_str" -> action = value + "uin_str1" -> from = bot.getFriend(value.toLong()) + "uin_str2" -> target = bot.getFriend(value.toLong()) + "suffix_str" -> suffix = value + } + } + return@lambda528 sequenceOf(BotNudgedEvent(from, action, suffix)) + + } + else -> { + bot.logger.debug { + "Unknown Transformers528 0x122L template\ntemplId=${body.templId}\nPermList=${body.msgTemplParam?._miraiContentToString()}" + } + return@lambda528 emptySequence() + } + } }, //好友输入状态 0x115L to lambda528 { bot -> @@ -579,12 +668,10 @@ internal object Transformers528 : Map by mapOf( val to = value.encodeToString() if (uin == bot.id) { val from = bot.nick - bot.cachedNick = to - add( - BotNickChangedEvent( - bot, from, to - ) - ) + if (from != to) { + bot.nick = to + add(BotNickChangedEvent(bot, from, to)) + } } else { val friend = (bot.getFriendOrNull(uin) ?: return@forEach) as FriendImpl val info = friend.friendInfo diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/LinkedList.common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/LinkedList.common.kt new file mode 100644 index 000000000..fb869d01e --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/LinkedList.common.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2019-2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AFFERO GENERAL PUBLIC LICENSE version 3 license that can be found via the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +package net.mamoe.mirai.qqandroid.utils + +// We target JVM and Android only. +internal expect class LinkedList() : List, Queue, Deque + +internal interface Queue : MutableCollection { + override fun add(element: E): Boolean + fun offer(element: E): Boolean + fun remove(): E + fun poll(): E + fun element(): E + fun peek(): E +} + +internal interface Deque : Queue { + fun addFirst(e: E) + fun addLast(e: E) + fun offerFirst(e: E): Boolean + fun offerLast(e: E): Boolean + fun removeFirst(): E + fun removeLast(): E + fun pollFirst(): E + fun pollLast(): E + val first: E + val last: E + fun peekFirst(): E + fun peekLast(): E + fun removeFirstOccurrence(o: E): Boolean + fun removeLastOccurrence(o: E): Boolean + fun push(e: E) + fun pop(): E + fun descendingIterator(): Iterator +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/LinkedList.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/LinkedList.kt new file mode 100644 index 000000000..291206682 --- /dev/null +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/LinkedList.kt @@ -0,0 +1,17 @@ +/* + * + * * Copyright 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.qqandroid.utils + +import java.util.LinkedList + +@Suppress("ACTUAL_WITHOUT_EXPECT") +internal actual typealias LinkedList = LinkedList \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt index 7b3957606..78a9ca73f 100644 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/utils/PlatformSocket.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.qqandroid.utils import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Closeable @@ -20,6 +21,7 @@ import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.net.Socket import java.net.SocketException +import java.util.concurrent.Executors import kotlin.coroutines.CoroutineContext /** @@ -38,6 +40,7 @@ internal actual class PlatformSocket : Closeable { if (::socket.isInitialized) { socket.close() } + thread.shutdownNow() } @PublishedApi @@ -67,15 +70,17 @@ internal actual class PlatformSocket : Closeable { } } + private val thread = Executors.newSingleThreadExecutor() + /** * @throws ReadPacketInternalException */ - actual suspend fun read(): ByteReadPacket { - return withContext(Dispatchers.IO) { - try { + actual suspend fun read(): ByteReadPacket = suspendCancellableCoroutine { cont -> + thread.submit { + kotlin.runCatching { readChannel.readPacketAtMost(Long.MAX_VALUE) - } catch (e: IOException) { - throw ReadPacketInternalException(e) + }.let { + cont.resumeWith(it) } } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index ffd4e4200..b8c5956a8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -20,6 +20,9 @@ import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.action.BotNudge +import net.mamoe.mirai.message.action.MemberNudge +import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.* import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* @@ -216,6 +219,15 @@ public abstract class Bot internal constructor( @JvmSynthetic public abstract suspend fun recall(source: MessageSource) + /** + * 创建一个 "戳一戳" 消息 + * + * @see MemberNudge.sendTo 发送这个戳一戳消息 + */ + @MiraiExperimentalAPI + @SinceMirai("1.3.0") + public fun nudge(): BotNudge = BotNudge(this) + /** * 获取图片下载链接 * @@ -339,6 +351,14 @@ public abstract class Bot internal constructor( @JvmSynthetic public abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) + @Deprecated( + "use member function.", + replaceWith = ReplaceWith("nudge.sendTo(contact)"), + level = DeprecationLevel.ERROR + ) + @SinceMirai("1.3.0") + public abstract suspend fun sendNudge(nudge: Nudge, receiver: Contact): Boolean + // endregion /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt index 6cdf585cf..bd4dde5e8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.Context +import net.mamoe.mirai.utils.SinceMirai import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -45,6 +46,9 @@ public expect interface BotFactory { passwordMd5: ByteArray, configuration: BotConfiguration = BotConfiguration.Default ): Bot + + @SinceMirai("1.3.0") + public companion object INSTANCE : BotFactory } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt index dce042719..4163ebddd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt @@ -18,10 +18,13 @@ import net.mamoe.mirai.event.events.FriendMessagePostSendEvent import net.mamoe.mirai.event.events.FriendMessagePreSendEvent import net.mamoe.mirai.message.FriendMessageEvent import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.action.FriendNudge import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.recall +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai import kotlin.jvm.JvmSynthetic /** @@ -70,6 +73,15 @@ public abstract class Friend : User(), CoroutineScope { @JvmSynthetic abstract override suspend fun sendMessage(message: Message): MessageReceipt + /** + * 创建一个 "戳一戳" 消息 + * + * @see FriendNudge.sendTo 发送这个戳一戳消息 + */ + @MiraiExperimentalAPI + @SinceMirai("1.3.0") + public final override fun nudge(): FriendNudge = FriendNudge(this) + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "VIRTUAL_MEMBER_HIDDEN", "OVERRIDE_BY_INLINE") @kotlin.internal.InlineOnly @JvmSynthetic diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt index d89ec6f24..f336fce9e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -16,10 +16,14 @@ import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.event.events.* import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.action.MemberNudge +import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.recall +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.WeakRefProperty import kotlin.jvm.JvmSynthetic import kotlin.time.Duration @@ -157,6 +161,15 @@ public abstract class Member : MemberJavaFriendlyAPI, User() { @JvmSynthetic public abstract override suspend fun sendMessage(message: Message): MessageReceipt + /** + * 创建一个 "戳一戳" 消息 + * + * @see MemberNudge.sendTo 发送这个戳一戳消息 + */ + @MiraiExperimentalAPI + @SinceMirai("1.3.0") + public final override fun nudge(): Nudge = MemberNudge(this) + /** * @see sendMessage */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt index dcd865768..b49888d70 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/User.kt @@ -15,11 +15,17 @@ import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.action.FriendNudge +import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.PlainText +import net.mamoe.mirai.message.data.isContentEmpty +import net.mamoe.mirai.message.recall import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.OverFileSizeMaxException +import net.mamoe.mirai.utils.SinceMirai import kotlin.jvm.JvmSynthetic /** @@ -67,6 +73,15 @@ public abstract class User : Contact(), CoroutineScope { @JvmSynthetic public abstract override suspend fun sendMessage(message: Message): MessageReceipt + /** + * 创建一个 "戳一戳" 消息 + * + * @see FriendNudge.sendTo 发送这个戳一戳消息 + */ + @MiraiExperimentalAPI + @SinceMirai("1.3.0") + public abstract fun nudge(): Nudge + /** * @see sendMessage */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupHonorListData.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupHonorListData.kt new file mode 100644 index 000000000..356b121d7 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupHonorListData.kt @@ -0,0 +1,237 @@ +package net.mamoe.mirai.data + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai +import kotlin.jvm.JvmStatic + +/** + * 群荣誉信息 + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public enum class GroupHonorType(public val value: Int) { + TALKATIVE(1), // 龙王 + PERFORMER(2), // 群聊之火 + LEGEND(3), // 群聊炽焰 + STRONG_NEWBIE(5), // 冒尖小春笋 + EMOTION(6), // 快乐源泉 + ACTIVE(7), // 活跃头衔 + EXCLUSIVE(8), // 特殊头衔 + MANAGE(9); // 管理头衔 + internal companion object { + @JvmStatic + internal fun deserializeFromInt(value: Int): GroupHonorType = values().first { it.value == value } + } +} + +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +@Serializable +public data class GroupHonorListData( + @SerialName("acceptLanguages") + val acceptLanguages: List? = null, + + @SerialName("gc") + val gc: String?, + + @Serializable(with = GroupHonorTypeSerializer::class) + @SerialName("type") + val type: GroupHonorType?, + + @SerialName("uin") + val uin: String?, + + @SerialName("talkativeList") + val talkativeList: List? = null, + + @SerialName("currentTalkative") + val currentTalkative: CurrentTalkative? = null, + + @SerialName("actorList") + val actorList: List? = null, + + @SerialName("legendList") + val legendList: List? = null, + + @SerialName("newbieList") + val newbieList: List? = null, + + @SerialName("strongnewbieList") + val strongNewbieList: List? = null, + + @SerialName("emotionList") + val emotionList: List? = null, + + @SerialName("levelname") + val levelName: LevelName? = null, + + @SerialName("manageList") + val manageList: List? = null, + + @SerialName("exclusiveList") + val exclusiveList: List? = null, + + @SerialName("activeObj") + val activeObj: Map?>? = null, // Key为活跃等级名, 如`冒泡` + + @SerialName("showActiveObj") + val showActiveObj: Map? = null, + + @SerialName("myTitle") + val myTitle: String?, + + @SerialName("myIndex") + val myIndex: Int? = 0, + + @SerialName("myAvatar") + val myAvatar: String?, + + @SerialName("hasServerError") + val hasServerError: Boolean?, + + @SerialName("hwExcellentList") + val hwExcellentList: List? = null +) { + @Serializable + public data class Language( + @SerialName("code") + val code: String? = null, + + @SerialName("script") + val script: String? = null, + + @SerialName("region") + val region: String? = null, + + @SerialName("quality") + val quality: Double? = null + ) + + @Serializable + public data class Actor( + @SerialName("uin") + val uin: Long? = 0, + + @SerialName("avatar") + val avatar: String? = null, + + @SerialName("name") + val name: String? = null, + + @SerialName("desc") + val desc: String? = null, + + @SerialName("btnText") + val btnText: String? = null, + + @SerialName("text") + val text: String? = null, + + @SerialName("icon") + val icon: Int? + ) + + @Serializable + public data class Talkative( + @SerialName("uin") + val uin: Long? = 0, + + @SerialName("avatar") + val avatar: String? = null, + + @SerialName("name") + val name: String? = null, + + @SerialName("desc") + val desc: String? = null, + + @SerialName("btnText") + val btnText: String? = null, + + @SerialName("text") + val text: String? = null + ) + + @Serializable + public data class CurrentTalkative( + @SerialName("uin") + val uin: Long? = 0, + + @SerialName("day_count") + val dayCount: Int? = null, + + @SerialName("avatar") + val avatar: String? = null, + + @SerialName("avatar_size") + val avatarSize: Int? = null, + + @SerialName("nick") + val nick: String? = null + ) + + @Serializable + public data class LevelName( + @SerialName("lvln1") + val lv1: String? = null, + + @SerialName("lvln2") + val lv2: String? = null, + + @SerialName("lvln3") + val lv3: String? = null, + + @SerialName("lvln4") + val lv4: String? = null, + + @SerialName("lvln5") + val lv5: String? = null, + + @SerialName("lvln6") + val lv6: String? = null + ) + + @Serializable + public data class Tag( + @SerialName("uin") + val uin: Long? = 0, + + @SerialName("avatar") + val avatar: String? = null, + + @SerialName("name") + val name: String? = null, + + @SerialName("btnText") + val btnText: String? = null, + + @SerialName("text") + val text: String? = null, + + @SerialName("tag") + val tag: String? = null, // 头衔 + + @SerialName("tagColor") + val tagColor: String? = null + ) + + @Serializer(forClass = GroupHonorType::class) + public object GroupHonorTypeSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("GroupHonorTypeSerializer", PrimitiveKind.INT) + + override fun serialize(encoder: Encoder, value: GroupHonorType) { + encoder.encodeInt(value.value) + } + + override fun deserialize(decoder: Decoder): GroupHonorType { + return GroupHonorType.deserializeFromInt(decoder.decodeInt()) + } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt index 71ada0eb1..be17801be 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt @@ -9,13 +9,10 @@ package net.mamoe.mirai.data -import net.mamoe.mirai.Bot import net.mamoe.mirai.LowLevelAPI /** * 群资料. - * - * 通过 [Bot._lowLevelQueryGroupInfo] 得到 */ @LowLevelAPI public interface GroupInfo { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/README.md b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/README.md index c46f489b3..02167d9ce 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/README.md +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/README.md @@ -21,6 +21,8 @@ - 服务器主动要求更换另一个服务器: RequireReconnect - Bot 重新登录: BotReloginEvent - Bot 头像改变: BotAvatarChangedEvent +- (`1.2.0+`) Bot 昵称改变: BotNickChangedEvent +- (`1.3.0+`) Bot 被戳: BotNudgedEvent ### [消息](message.kt) - (`1.1.0-`) 主动发送消息: MessageSendEvent @@ -81,13 +83,17 @@ ##### 成员权限 - 成员权限改变: MemberPermissionChangeEvent -##### 禁言 +##### 动作 - 群成员被禁言: MemberMuteEvent - 群成员被取消禁言: MemberUnmuteEvent +- (`1.3.0+`) 群员被戳: MemberNudgedEvent ### [好友](friend.kt) - 好友昵称改变: FriendRemarkChangeEvent - 成功添加了一个新好友: FriendAddEvent - 好友已被删除: FriendDeleteEvent - 一个账号请求添加机器人为好友: NewFriendRequestEvent -- 好友头像改变: FriendAvatarChangedEvent \ No newline at end of file +- 好友头像改变: FriendAvatarChangedEvent +- (`1.2.0+`) 好友昵称改变: FriendNickChangedEvent +- (`1.2.0+`) 好友输入状态改变: FriendInputStatusChangedEvent +- (`1.3.0+`) 好友被戳: FriendNudgedEvent diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/bot.kt index 372293f00..2db8de6aa 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/bot.kt @@ -14,7 +14,9 @@ package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.AbstractEvent +import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI @@ -124,6 +126,31 @@ public data class BotNickChangedEvent( public val to: String ) : BotEvent, Packet, AbstractEvent() +/** + * [Bot] 被 [戳][Nudge] 的事件. + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public data class BotNudgedEvent internal constructor( + /** + * 戳一戳的发起人,为 [Bot] 的某一好友, 或某一群员 + */ + public val from: User, + /** + * 戳一戳的动作名称 + */ + public val action: String, + /** + * 戳一戳中设置的自定义后缀 + */ + public val suffix: String, +) : BotEvent, Packet, AbstractEvent() { + /** + * 戳一戳的目标 + */ + public override val bot: Bot get() = from.bot +} + // region 图片 // endregion diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/friend.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/friend.kt index d329d228c..41cbc64d5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/friend.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/friend.kt @@ -112,8 +112,6 @@ public data class FriendAvatarChangedEvent internal constructor( public override val friend: Friend ) : FriendEvent, Packet, AbstractEvent() - - /** * [Friend] 昵称改变事件, 在此事件广播时好友已经完成改名 * @see BotNickChangedEvent @@ -124,7 +122,7 @@ public data class FriendNickChangedEvent internal constructor( public val from: String, public val to: String ) : FriendEvent, Packet, AbstractEvent() - + /** * 好友输入状态改变的事件,当开始输入文字、退出聊天窗口或清空输入框时会触发此事件 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt index 5135df88d..c6e53b96a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/group.kt @@ -22,8 +22,10 @@ import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.internal.MiraiAtomicBoolean +import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.internal.runBlocking import kotlin.internal.LowPriorityInOverloadResolution import kotlin.jvm.* @@ -130,6 +132,18 @@ public sealed class BotJoinGroupEvent : GroupEvent, BotPassiveEvent, Packet, Abs return "BotJoinGroupEvent.Invite(invitor=$invitor)" } } + + /** + * 原群主通过 https://huifu.qq.com/ 恢复原来群主身份并入群, + * [Bot] 是原群主 + */ + @MiraiExperimentalAPI + @SinceMirai("1.3.0") + public data class Retrieve internal constructor( + public override val group: Group + ) : BotJoinGroupEvent() { + override fun toString(): String = "MemberJoinEvent.Retrieve(group=${group.id})" + } } // region 群设置 @@ -260,6 +274,17 @@ public sealed class MemberJoinEvent( ) : MemberJoinEvent(member) { public override fun toString(): String = "MemberJoinEvent.Active(member=${member.id})" } + + /** + * 原群主通过 https://huifu.qq.com/ 恢复原来群主身份并入群, + * 此时 [member] 的 [Member.permission] 肯定是 [MemberPermission.OWNER] + */ + @SinceMirai("1.3.0") + public data class Retrieve internal constructor( + public override val member: Member + ) : MemberJoinEvent(member) { + override fun toString(): String = "MemberJoinEvent.Retrieve(member=${member.id})" + } } /** @@ -487,4 +512,33 @@ public data class MemberUnmuteEvent internal constructor( // endregion +// region 戳一戳 + + +/** + * [Member] 被 [戳][Nudge] 的事件. + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public data class MemberNudgedEvent internal constructor( + /** + * 戳一戳的发起人, 不可能是 bot + */ + public val from: Member, + /** + * 戳一戳的目标 (被戳的群员), 不可能是 bot + */ + public override val member: Member, + /** + * 戳一戳的动作名称 + */ + public val action: String, + /** + * 戳一戳中设置的自定义后缀 + */ + public val suffix: String, +) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent() + +// endregion + // endregion diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt index a6816670b..050b1f859 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt @@ -55,13 +55,6 @@ public interface LowLevelBotAPIAccessor { @LowLevelAPI public suspend fun _lowLevelQueryGroupList(): Sequence - /** - * 向服务器查询群资料. 获得的仅为当前时刻的资料. - * 请优先使用 [Bot.getGroup] 然后查看群资料. - */ - @LowLevelAPI - public suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo - /** * 向服务器查询群成员列表. * 请优先使用 [Bot.getGroup], [Group.members] 查看群成员. @@ -122,6 +115,15 @@ public interface LowLevelBotAPIAccessor { public suspend fun _lowLevelGetGroupActiveData(groupId: Long, page: Int = -1): GroupActiveData + /** + * 获取群荣誉信息 + */ + @SinceMirai("1.3.0") + @LowLevelAPI + @MiraiExperimentalAPI + public suspend fun _lowLevelGetGroupHonorListData(groupId: Long, type: GroupHonorType): GroupHonorListData? + + /** * 处理一个账号请求添加机器人为好友的事件 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/action/Nudge.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/action/Nudge.kt new file mode 100644 index 000000000..927ec79df --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/action/Nudge.kt @@ -0,0 +1,118 @@ +/* + * + * * Copyright 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.message.action + +import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.event.events.BotNudgedEvent +import net.mamoe.mirai.event.events.MemberNudgedEvent +import net.mamoe.mirai.utils.BotConfiguration +import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai + +/** + * 一个 "戳一戳" 消息. + * + * 仅在手机 QQ 8.4.0 左右版本才受支持. 其他客户端会忽略这些消息. + * + * @see User.nudge 创建 [Nudge] 对象 + * @see Bot.nudge 创建 [Nudge] 对象 + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public sealed class Nudge { + /** + * 戳的对象. 即 "A 戳了 B" 中的 "B". + */ + public abstract val target: ContactOrBot // User or Bot + + /** + * 发送戳一戳该成员的消息. + * + * 需要 [使用协议][BotConfiguration.protocol] [MiraiProtocol.ANDROID_PHONE]. + * + * @param receiver 这条 "戳一戳" 消息的接收对象. (不是 "戳" 动作的对象, 而是接收 "A 戳了 B" 这条消息的对象) + * + * @see MemberNudgedEvent 成员被戳事件 + * @see BotNudgedEvent [Bot] 被戳事件 + * + * @throws UnsupportedOperationException 当未使用 [安卓协议][MiraiProtocol.ANDROID_PHONE] 时抛出 + * + * @return 成功发送时为 `true`. 若对方禁用 "戳一戳" 功能, 返回 `false`. + */ + @JvmBlockingBridge + @MiraiExperimentalAPI + public suspend fun sendTo(receiver: Contact): Boolean { + @Suppress("DEPRECATION_ERROR") + return receiver.bot.sendNudge(this, receiver) + } + + public companion object { + /** + * 发送戳一戳该成员的消息. + * + * 需要 [使用协议][BotConfiguration.protocol] [MiraiProtocol.ANDROID_PHONE]. + * + * @see MemberNudgedEvent 成员被戳事件 + * @see BotNudgedEvent [Bot] 被戳事件 + * + * @throws UnsupportedOperationException 当未使用 [安卓协议][MiraiProtocol.ANDROID_PHONE] 时抛出 + * + * @return 成功发送时为 `true`. 若对方禁用 "戳一戳" 功能, 返回 `false`. + */ + @MiraiExperimentalAPI + @JvmBlockingBridge + public suspend fun Contact.sendNudge(nudge: Nudge): Boolean = nudge.sendTo(this) + } +} + +/** + * @see Bot.nudge + * @see Nudge + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public data class BotNudge( + public override val target: Bot +) : Nudge() + +/** + * @see User.nudge + * @see Nudge + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public sealed class UserNudge : Nudge() { + public abstract override val target: User +} + +/** + * @see Member.nudge + * @see Nudge + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public data class MemberNudge( + public override val target: Member +) : UserNudge() + +/** + * @see Friend.nudge + * @see Nudge + */ +@MiraiExperimentalAPI +@SinceMirai("1.3.0") +public data class FriendNudge( + public override val target: Friend +) : UserNudge() \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Voice.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Voice.kt index f564ab225..ce9538f4d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Voice.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Voice.kt @@ -47,9 +47,11 @@ public class Voice @MiraiInternalAPI constructor( } public val url: String? - get() = - if (_url.startsWith("http")) _url - else null + get() = when { + _url.isBlank() -> null + _url.startsWith("http") -> _url + else -> "http://grouptalk.c2c.qq.com$_url" + } private var _stringValue: String? = null get() = field ?: kotlin.run { diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactory.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactory.kt index 2d5a4fa54..4cbef2656 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactory.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactory.kt @@ -15,6 +15,7 @@ package net.mamoe.mirai import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.ContextImpl +import net.mamoe.mirai.utils.SinceMirai /** * 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式. @@ -52,6 +53,16 @@ public actual interface BotFactory { configuration: BotConfiguration ): Bot + @SinceMirai("1.3.0") + public actual companion object INSTANCE : BotFactory { + override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot { + return factory.Bot(context, qq, password, configuration) + } + + override fun Bot(context: Context, qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot { + return factory.Bot(context, qq, passwordMd5, configuration) + } + } } /** @@ -61,7 +72,12 @@ public actual interface BotFactory { */ @JvmName("newBot") @JvmOverloads -public fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = +public fun Bot( + context: Context, + qq: Long, + password: String, + configuration: BotConfiguration = BotConfiguration.Default +): Bot = factory.Bot(context, qq, password, configuration) /** @@ -109,7 +125,12 @@ public fun Bot( * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 */ @JvmSynthetic -public inline fun Bot(context: Context, qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot = +public inline fun Bot( + context: Context, + qq: Long, + passwordMd5: ByteArray, + configuration: (BotConfiguration.() -> Unit) +): Bot = factory.Bot(context, qq, passwordMd5, BotConfiguration().apply(configuration)) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt index 91ebcb778..a8f9a782f 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformLogger.jvm.kt @@ -59,7 +59,7 @@ public actual open class PlatformLogger @JvmOverloads constructor( */ @SinceMirai("1.1.0") protected open fun printLog(message: String?, priority: SimpleLogger.LogPriority) { - if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message") + if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message${Color.RESET}") else output("$currentTimeFormatted ${priority.simpleName}/$identity: $message") } @@ -143,4 +143,4 @@ internal val Throwable.stackTraceString get() = ByteArrayOutputStream().run { printStackTrace(PrintStream(this)) String(this.toByteArray()) - } \ No newline at end of file + }