From 1117c14c7dc37715e43dfa53abf3f70e6a764c60 Mon Sep 17 00:00:00 2001 From: sandtechnology <20417547+sandtechnology@users.noreply.github.com> Date: Fri, 1 Jan 2021 21:39:45 +0800 Subject: [PATCH] Support Stranger & Fix #664 (#795) * Introduce UserInfo and change uin to id * Stranger api: List and asStranger * Stranger api: Events * Stranger api: MessageDSL and MessageSource * Update docs * Improve LoginSolver fix project-mirai/mirai-login-solver-selenium#4 * Improve SeleniumLoginSolver loading * Fix MessageChain.fragmented * 2.0-M2-1 * Stranger api change: StrangerRelationChangeEvent * Stranger impl: list and asStranger * Stranger impl: get stranger list and del stranger protocol * Stranger impl: add stranger protocol * Stranger impl: MessageSource refactor and sending&receiving message * Stranger impl: Dropping long message support (server rejected) * Stranger impl: sending stranger message when member is stranger * Fix wrong key Co-authored-by: Him188 * Add @LowLevelApi Co-authored-by: Him188 * Remove redundant val modifier * Replace nudge if statement to when statement * Move list seq to QQAndroidClient * Replace id with uin in UserInfo and fix build error * Using by lazy in asStranger * Fix wrong wordings * Fix wrong wordings in uploading image * Fix nudge Co-authored-by: Him188 Co-authored-by: Karlatemp --- mirai-core-api/src/commonMain/kotlin/Bot.kt | 23 ++++ .../src/commonMain/kotlin/contact/Member.kt | 6 + .../src/commonMain/kotlin/contact/Stranger.kt | 96 +++++++++++++++ .../src/commonMain/kotlin/data/FriendInfo.kt | 8 +- .../src/commonMain/kotlin/data/MemberInfo.kt | 2 +- .../commonMain/kotlin/data/StrangerInfo.kt | 24 ++++ .../src/commonMain/kotlin/data/UserInfo.kt | 12 ++ .../kotlin/event/MessageSubscribersBuilder.kt | 9 ++ .../src/commonMain/kotlin/event/events/bot.kt | 38 ++++-- .../commonMain/kotlin/event/events/friend.kt | 10 +- .../commonMain/kotlin/event/events/message.kt | 57 +++++++++ .../kotlin/event/events/stranger.kt | 114 ++++++++++++++++++ .../commonMain/kotlin/event/events/types.kt | 9 ++ .../src/commonMain/kotlin/lowLevelApi.kt | 10 ++ .../commonMain/kotlin/message/action/Nudge.kt | 8 ++ .../kotlin/message/data/MessageSource.kt | 35 +++++- .../message/data/MessageSourceBuilder.kt | 6 +- mirai-core/src/commonMain/kotlin/MiraiImpl.kt | 15 ++- .../src/commonMain/kotlin/QQAndroidBot.kt | 3 + .../commonMain/kotlin/contact/AbstractUser.kt | 25 +++- .../kotlin/contact/MemberInfoImpl.kt | 3 +- .../kotlin/contact/NormalMemberImpl.kt | 5 + .../commonMain/kotlin/contact/StrangerImpl.kt | 91 ++++++++++++++ .../src/commonMain/kotlin/contact/util.kt | 55 +++++++++ .../commonMain/kotlin/message/conversions.kt | 23 ++-- .../kotlin/message/incomingSourceImpl.kt | 54 +++++++-- .../kotlin/message/offlineSourceImpl.kt | 19 ++- .../kotlin/message/outgoingSourceImpl.kt | 22 +++- .../network/QQAndroidBotNetworkHandler.kt | 28 +++++ .../kotlin/network/QQAndroidClient.kt | 1 + .../network/protocol/data/proto/FrdSysMsg.kt | 110 +++++++++++++++++ .../network/protocol/data/proto/OIDB.kt | 88 ++++++++++++++ .../network/protocol/packet/PacketFactory.kt | 5 +- .../chat/receive/MessageSvc.PbGetMsg.kt | 97 ++++++++++++--- .../chat/receive/MessageSvc.PbSendMsg.kt | 62 +++++++++- .../chat/receive/OnlinePush.PbPushGroupMsg.kt | 15 ++- .../packet/chat/receive/OnlinePush.ReqPush.kt | 80 +++++++----- .../protocol/packet/list/StrangerList.kt | 106 ++++++++++++++++ 38 files changed, 1260 insertions(+), 114 deletions(-) create mode 100644 mirai-core-api/src/commonMain/kotlin/contact/Stranger.kt create mode 100644 mirai-core-api/src/commonMain/kotlin/data/StrangerInfo.kt create mode 100644 mirai-core-api/src/commonMain/kotlin/data/UserInfo.kt create mode 100644 mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt create mode 100644 mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt create mode 100644 mirai-core/src/commonMain/kotlin/network/protocol/data/proto/FrdSysMsg.kt create mode 100644 mirai-core/src/commonMain/kotlin/network/protocol/packet/list/StrangerList.kt diff --git a/mirai-core-api/src/commonMain/kotlin/Bot.kt b/mirai-core-api/src/commonMain/kotlin/Bot.kt index e4b151838..a7bbf99cc 100644 --- a/mirai-core-api/src/commonMain/kotlin/Bot.kt +++ b/mirai-core-api/src/commonMain/kotlin/Bot.kt @@ -93,11 +93,34 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot { */ public val asFriend: Friend + /** + * [User.id] 与 [Bot.id] 相同的 [Stranger] 实例 + */ + public val asStranger: Stranger + + + /** + * 陌生人列表. 与服务器同步更新. + */ + public val strangers: ContactList + + /** + * 以 [对方 QQ 号码][id] 获取一个陌生人对象, 在获取失败时返回 `null`. + */ + public fun getStranger(id: Long): Stranger? = + strangers.firstOrNull { it.id == id } + + /** + * 以 [对方 QQ 号码][id] 获取一个陌生人对象, 在获取失败时抛出 [NoSuchElementException]. + */ + public fun getStrangerOrFail(id: Long): Stranger = getStranger(id) ?: throw NoSuchElementException("stranger $id") + /** * 好友列表. 与服务器同步更新. */ public val friends: ContactList + /** * 以 [对方 QQ 号码][id] 获取一个好友对象, 在获取失败时返回 `null`. * 在 [id] 与 [Bot.id] 相同时返回 [Bot.asFriend] diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Member.kt b/mirai-core-api/src/commonMain/kotlin/contact/Member.kt index c20c21dbe..ea301b715 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Member.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Member.kt @@ -137,6 +137,12 @@ public fun Member.asFriend(): Friend = this.bot.getFriend(this.id) ?: error("$th */ public fun Member.asFriendOrNull(): Friend? = this.bot.getFriend(this.id) +/** + * 得到此成员作为陌生人的对象, 当此成员不是陌生人时返回 `null` + */ +public fun Member.asStrangerOrNull(): Stranger? = this.bot.getStranger(this.id) + + /** * 判断此成员是否为好友 */ diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Stranger.kt b/mirai-core-api/src/commonMain/kotlin/contact/Stranger.kt new file mode 100644 index 000000000..b9486faa5 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/contact/Stranger.kt @@ -0,0 +1,96 @@ +/* + * 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 + */ + +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "UnusedImport") + +package net.mamoe.mirai.contact + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.action.Nudge +import net.mamoe.mirai.message.action.StrangerNudge +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.isContentEmpty +import net.mamoe.mirai.message.data.toPlainText +import net.mamoe.mirai.utils.MiraiExperimentalApi + +/** + * 代表一位陌生人. + * + * 一个 [Stranger] 实例并不是独立的, 它属于一个 [Bot]. + * 对于同一个 [Bot], 任何一个人的 [Stranger] 实例都是单一的. + * [Stranger] 无法通过任何方式直接构造. 任何时候都应从 [Bot.getStranger] 或事件中获取. + * + * 陌生人的来源:当将添加好友设置为 + * ‘任何人可添加为好友’或‘需要回答对验证问题时’ + * 且被他人成功添加时此人会成为陌生人 + * + * 陌生人需要主动添加好友才能构成好友关系 + * 但 Mirai 将不会提供此功能 + * 请手动在其他客户端添加好友 + * + * @see StrangerMessageEvent + */ +public interface Stranger : User, CoroutineScope { + /** + * QQ 号码 + */ + public override val id: Long + + /** + * 昵称 + */ + public override val nick: String + + + /** + * 向这个对象发送消息. + * + * 单条消息最大可发送 4500 字符或 50 张图片. + * + * @see FriendMessagePreSendEvent 发送消息前事件 + * @see FriendMessagePostSendEvent 发送消息后事件 + * + * @throws EventCancelledException 当发送消息事件被取消时抛出 + * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 + * @throws MessageTooLargeException 当消息过长时抛出 + * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmBlockingBridge + public override suspend fun sendMessage(message: Message): MessageReceipt + + /** + * 删除并屏蔽该陌生人, 屏蔽后对方将无法发送临时会话消息 + * + * @see StrangerRelationChangeEvent.Deleted 陌生人删除事件 + */ + @JvmBlockingBridge + public suspend fun delete() + + /** + * 发送纯文本消息 + * @see sendMessage + */ + @JvmBlockingBridge + public override suspend fun sendMessage(message: String): MessageReceipt = + this.sendMessage(message.toPlainText()) + + /** + * 创建一个 "戳一戳" 消息 + * + * @see Nudge.sendTo 发送这个戳一戳消息 + */ + @MiraiExperimentalApi + public override fun nudge(): StrangerNudge = StrangerNudge(this) +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt b/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt index 96f4c592b..a56fb0739 100644 --- a/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt +++ b/mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt @@ -12,12 +12,12 @@ package net.mamoe.mirai.data import net.mamoe.mirai.LowLevelApi @LowLevelApi -public interface FriendInfo { - public val uin: Long +public interface FriendInfo : UserInfo { + public override val uin: Long - public val nick: String + public override val nick: String - public val remark: String + public override val remark: String } @LowLevelApi diff --git a/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt b/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt index 6f36c121f..cde187c46 100644 --- a/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt +++ b/mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt @@ -13,7 +13,7 @@ import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.MemberPermission @LowLevelApi -public interface MemberInfo : FriendInfo { +public interface MemberInfo : UserInfo { public val nameCard: String public val permission: MemberPermission diff --git a/mirai-core-api/src/commonMain/kotlin/data/StrangerInfo.kt b/mirai-core-api/src/commonMain/kotlin/data/StrangerInfo.kt new file mode 100644 index 000000000..b69d3c0c6 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/data/StrangerInfo.kt @@ -0,0 +1,24 @@ +package net.mamoe.mirai.data + +import net.mamoe.mirai.LowLevelApi + +@LowLevelApi +public interface StrangerInfo : UserInfo { + /** + * 陌生人的QQ号码 + */ + public override val uin: Long + + /** + * 陌生人的昵称 + * + */ + public override val nick: String + + /** + * 陌生人来源的群 + * + * 当不是来源于群时为0 + */ + public val fromGroup: Long +} diff --git a/mirai-core-api/src/commonMain/kotlin/data/UserInfo.kt b/mirai-core-api/src/commonMain/kotlin/data/UserInfo.kt new file mode 100644 index 000000000..1f09ed145 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/data/UserInfo.kt @@ -0,0 +1,12 @@ +package net.mamoe.mirai.data + +import net.mamoe.mirai.LowLevelApi + +@LowLevelApi +public interface UserInfo { + public val uin: Long + + public val nick: String + + public val remark: String +} diff --git a/mirai-core-api/src/commonMain/kotlin/event/MessageSubscribersBuilder.kt b/mirai-core-api/src/commonMain/kotlin/event/MessageSubscribersBuilder.kt index 41912730b..606b8fedd 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/MessageSubscribersBuilder.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/MessageSubscribersBuilder.kt @@ -258,6 +258,15 @@ public open class MessageSubscribersBuilder): Ret = + content({ this is StrangerMessageEvent }) { onEvent(this as StrangerMessageEvent, it) } + + /** 如果是陌生人发来的消息 */ + @MessageDsl + public fun sentByStranger(): ListeningFilter = newListeningFilter { this is StrangerMessageEvent } + /** 如果是群临时会话消息 */ @MessageDsl public fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessageEvent } 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 c9bdc5582..a3ffca114 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/bot.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/bot.kt @@ -171,33 +171,55 @@ public sealed class BotNudgedEvent : AbstractEvent(), BotEvent, Packet { @MiraiExperimentalApi /** [Bot] 在私聊中被戳 */ - public sealed class InPrivateSession : BotNudgedEvent(), FriendEvent { - abstract override val from: Friend + public sealed class InPrivateSession : BotNudgedEvent() { + abstract override val from: User override val bot: Bot get() = from.bot - override val friend: Friend get() = from /** 在私聊中 [Friend] 戳了 [Bot] */ public data class ByFriend internal constructor( override val friend: Friend, override val action: String, override val suffix: String - ) : InPrivateSession() { + ) : InPrivateSession(), FriendEvent { override val from: Friend get() = friend + override val bot: Bot get() = from.bot + override fun toString(): String { return "BotNudgedEvent.InPrivateSession.ByFriend(friend=$friend, action=$action, suffix=$suffix)" } } + /** 在私聊中 [Stranger] 戳了 [Bot] */ + public data class ByStranger internal constructor( + override val stranger: Stranger, + override val action: String, + override val suffix: String + ) : InPrivateSession(), StrangerEvent { + override val from: Stranger get() = stranger + override val bot: Bot get() = stranger.bot + + override fun toString(): String { + return "BotNudgedEvent.InPrivateSession.ByFriend(friend=$stranger, action=$action, suffix=$suffix)" + } + } + /** [Bot] 在私聊中自己戳了自己 */ public data class ByBot internal constructor( /** [Bot] 的对话对象 */ - override val friend: Friend, + /** 可能是 [Stranger] 或 [Friend] */ + val user: User, override val action: String, override val suffix: String - ) : InPrivateSession() { - override val from: Friend get() = bot.asFriend + ) : InPrivateSession(), BotEvent { + override val from: User + get() = if (user is Stranger) { + bot.asStranger + } else { + bot.asFriend + } + override fun toString(): String { - return "BotNudgedEvent.InPrivateSession.ByBot(friend=$friend, action=$action, suffix=$suffix)" + return "BotNudgedEvent.InPrivateSession.ByBot(friend=$user, action=$action, suffix=$suffix)" } } } diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt b/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt index 7c5ef00d0..457282f7c 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/friend.kt @@ -46,7 +46,7 @@ public data class FriendAddEvent internal constructor( ) : FriendEvent, Packet, AbstractEvent() /** - * 好友已被删除的事件. + * 好友已被删除或主动删除的事件. */ public data class FriendDeleteEvent internal constructor( public override val friend: Friend @@ -146,9 +146,9 @@ public sealed class FriendNudgedEvent : AbstractEvent(), FriendEvent, Packet { /** 在 [Bot] 与 [Friend] 的对话中 [Friend] 戳了自己事件 */ @MiraiExperimentalApi public data class NudgedByHimself internal constructor( + override val friend: Friend, override val action: String, - override val suffix: String, - override val friend: Friend + override val suffix: String ) : FriendNudgedEvent() { override fun toString(): String { return "FriendNudgedEvent.NudgedByHimself(friend=$friend, action=$action, suffix=$suffix)" @@ -161,9 +161,9 @@ public sealed class FriendNudgedEvent : AbstractEvent(), FriendEvent, Packet { /** [Bot] 戳了 [Friend] */ @MiraiExperimentalApi public data class NudgedByBot internal constructor( + override val friend: Friend, override val action: String, - override val suffix: String, - override val friend: Friend + override val suffix: String ) : FriendNudgedEvent() { override fun toString(): String { return "FriendNudgedEvent.NudgedByBot(friend=$friend, action=$action, suffix=$suffix)" 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 f35344d82..820883822 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/message.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/message.kt @@ -103,6 +103,16 @@ public data class TempMessagePreSendEvent @MiraiInternalApi constructor( public val group: Group get() = target.group } +/** + * 在发送陌生人消息前广播的事件. + * @see MessagePreSendEvent + */ +public data class StrangerMessagePreSendEvent @MiraiInternalApi constructor( + /** 发信目标. */ + public override val target: Stranger, + /** 待发送的消息. 修改后将会同时应用于发送. */ + public override var message: Message +) : UserMessagePreSendEvent() // endregion @@ -251,6 +261,27 @@ public data class TempMessagePostSendEvent @MiraiInternalApi constructor( public val group: Group get() = target.group } +/** + * 在陌生人消息发送后广播的事件. + * @see MessagePostSendEvent + */ +public data class StrangerMessagePostSendEvent @MiraiInternalApi constructor( + /** 发信目标. */ + public override val target: Stranger, + /** 待发送的消息. 此为 [MessagePreSendEvent.message] 的最终值. */ + public override val message: MessageChain, + /** + * 发送消息时抛出的异常. `null` 表示消息成功发送. + * @see result + */ + public override val exception: Throwable?, + /** + * 发送消息成功时的回执. `null` 表示消息发送失败. + * @see result + */ + public override val receipt: MessageReceipt? +) : UserMessagePostSendEvent() + // endregion // region MessageRecallEvent @@ -620,6 +651,32 @@ public class TempMessageEvent( "TempMessageEvent(sender=${sender.id} from group(${sender.group.id}), message=$message)" } +/** + * 机器人收到的陌生人消息的事件 + * + * @see MessageEvent + */ +@Suppress("DEPRECATION") +public class StrangerMessageEvent constructor( + public override val sender: Stranger, + public override val message: MessageChain, + public override val time: Int +) : AbstractMessageEvent(), MessageEvent, MessageEventExtensions, BroadcastControllable, StrangerEvent { + init { + val source = + message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") + check(source is OnlineMessageSource.Incoming.FromStranger) { "source provided to a StrangerMessage must be an instance of OnlineMessageSource.Incoming.FromStranger" } + } + + public override val stranger: Stranger get() = sender + public override val bot: Bot get() = super.bot + public override val subject: Stranger get() = sender + public override val senderName: String get() = sender.nick + public override val source: OnlineMessageSource.Incoming.FromStranger get() = message.source as OnlineMessageSource.Incoming.FromStranger + + public override fun toString(): String = "StrangerMessageEvent(sender=${sender.id}, message=$message)" +} + /** * 来自 [User] 的消息 * diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt b/mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt new file mode 100644 index 000000000..8e368dca3 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt @@ -0,0 +1,114 @@ +package net.mamoe.mirai.event.events + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.Stranger +import net.mamoe.mirai.event.AbstractEvent +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.message.action.Nudge +import net.mamoe.mirai.utils.MiraiExperimentalApi + +/** + * 新增陌生人的事件 + * + */ +public data class StrangerAddEvent internal constructor( + /** + * 新的陌生人. 已经添加到 [Bot.strangers] + */ + public override val stranger: Stranger +) : StrangerEvent, Packet, AbstractEvent() + + +/** + * 陌生人关系改变事件 + * + */ +public abstract class StrangerRelationChangeEvent( + public override val stranger: Stranger +) : StrangerEvent, Packet, AbstractEvent() { + /** + * 主动删除陌生人或陌生人被删除的事件 + * + * 除主动删除外,此事件为惰性广播,无法确保实时性 + * 目前被动删除仅会在陌生人二次添加时才会进行广播 + */ + public class Deleted( + /** + * 被删除的陌生人 + */ + stranger: Stranger + ) : StrangerRelationChangeEvent(stranger) + + /** + * 与陌生人成为好友 + */ + public class Friended( + /** + * 成为好友的陌生人 + * + * 成为好友后该陌生人会从陌生人列表中删除 + */ + public override val stranger: Stranger, + /** + * 成为好友后的实例 + * + * 已经添加到Bot的好友列表中 + */ + public val friend: Friend + ) : StrangerRelationChangeEvent(stranger) + +} + +/** + * 在 [Stranger] 与 [Bot] 的对话中, [Stranger] 被 [戳][Nudge] 事件 + * + * 注: 此事件仅可能在私聊中发生 + */ +@MiraiExperimentalApi +public sealed class StrangerNudgedEvent : AbstractEvent(), StrangerEvent, Packet { + /** + * 戳一戳的发起人, 为 [Bot] 的某一好友, 或是 [Bot.asFriend] + */ + public abstract val from: Stranger + + /** + * 戳一戳的动作名称 + */ + public abstract val action: String + + /** + * 戳一戳中设置的自定义后缀 + */ + public abstract val suffix: String + + /** 在 [Bot] 与 [Stranger] 的对话中 [Stranger] 戳了自己事件 */ + @MiraiExperimentalApi + public data class NudgedByHimself internal constructor( + override val stranger: Stranger, + override val action: String, + override val suffix: String + ) : StrangerNudgedEvent() { + override fun toString(): String { + return "StrangerNudgedEvent.NudgedByHimself(stranger=$stranger, action=$action, suffix=$suffix)" + } + + override val from: Stranger + get() = stranger + } + + /** [Bot] 戳了 [Stranger] */ + @MiraiExperimentalApi + public data class NudgedByBot internal constructor( + override val stranger: Stranger, + override val action: String, + override val suffix: String + ) : StrangerNudgedEvent() { + override fun toString(): String { + return "StrangerNudgedEvent.NudgedByBot(stranger=$stranger, action=$action, suffix=$suffix)" + } + + override val from: Stranger + get() = bot.asStranger + } +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/event/events/types.kt b/mirai-core-api/src/commonMain/kotlin/event/events/types.kt index 59b432dbc..b824160d9 100644 --- a/mirai-core-api/src/commonMain/kotlin/event/events/types.kt +++ b/mirai-core-api/src/commonMain/kotlin/event/events/types.kt @@ -17,6 +17,7 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.event.Event import kotlin.internal.HidesMembers @@ -91,4 +92,12 @@ public inline val GroupOperableEvent.operatorOrBot: Member public interface FriendEvent : BotEvent { public val friend: Friend public override val bot: Bot get() = friend.bot +} + +/** + * 有关陌生人的事件 + */ +public interface StrangerEvent : BotEvent { + public val stranger: Stranger + public override val bot: Bot get() = stranger.bot } \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/lowLevelApi.kt b/mirai-core-api/src/commonMain/kotlin/lowLevelApi.kt index c77365f98..350d0fb14 100644 --- a/mirai-core-api/src/commonMain/kotlin/lowLevelApi.kt +++ b/mirai-core-api/src/commonMain/kotlin/lowLevelApi.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.Job import net.mamoe.mirai.contact.AnonymousMember import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.data.* import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.WeakRef @@ -51,6 +52,15 @@ public interface LowLevelApiAccessor { @LowLevelApi public fun _lowLevelNewFriend(bot: Bot, friendInfo: FriendInfo): Friend + /** + * 构造一个 [Stranger] 对象. 它持有对 [Bot] 的弱引用([WeakRef]). + * + * [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job. + * 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭. + */ + @LowLevelApi + public fun _lowLevelNewStranger(bot: Bot, strangerInfo: StrangerInfo): Stranger + /** * 向服务器查询群列表. 返回值高 32 bits 为 uin, 低 32 bits 为 groupCode */ diff --git a/mirai-core-api/src/commonMain/kotlin/message/action/Nudge.kt b/mirai-core-api/src/commonMain/kotlin/message/action/Nudge.kt index 440e3cef4..c8413ed17 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/action/Nudge.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/action/Nudge.kt @@ -99,4 +99,12 @@ public data class MemberNudge( */ public data class FriendNudge( public override val target: Friend +) : UserNudge() + +/** + * @see Stranger.nudge + * @see Nudge + */ +public data class StrangerNudge( + public override val target: UserOrBot ) : UserNudge() \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt index b9655c412..40d6a6694 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt @@ -27,6 +27,7 @@ import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageSourceSerializerImpl import net.mamoe.mirai.message.data.MessageSource.Key.isAboutFriend import net.mamoe.mirai.message.data.MessageSource.Key.isAboutGroup +import net.mamoe.mirai.message.data.MessageSource.Key.isAboutStranger import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.utils.LazyProperty @@ -225,6 +226,17 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle { } } + /** + * 判断是否是发送给陌生人 或从陌生人接收的消息的消息源 + */ + @JvmStatic + public fun MessageSource.isAboutStranger(): Boolean { + return when (this) { + is OnlineMessageSource -> subject is Stranger + is OfflineMessageSource -> kind == MessageSourceKind.STRANGER + } + } + /** * 判断是否是发送给临时会话, 或从临时会话接收的消息的消息源 */ @@ -348,6 +360,14 @@ public sealed class OnlineMessageSource : MessageSource() { // final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.ids})" } + public abstract class ToStranger : Outgoing() { + public companion object Key : AbstractPolymorphicMessageKey(Outgoing, { it.safeCast() }) + + public abstract override val target: Stranger + public final override val subject: Stranger get() = target + // final override fun toString(): String = "OnlineMessageSource.ToFriend(target=${target.ids})" + } + public abstract class ToTemp : Outgoing() { public companion object Key : AbstractPolymorphicMessageKey(Outgoing, { it.safeCast() }) @@ -393,6 +413,15 @@ public sealed class OnlineMessageSource : MessageSource() { public final override val target: Bot get() = sender.bot } + public abstract class FromStranger : Incoming() { + public companion object Key : + AbstractPolymorphicMessageKey(Incoming, { it.safeCast() }) + + public abstract override val sender: Stranger + public final override val subject: Stranger get() = sender + public final override val target: Bot get() = sender.bot + } + public abstract class FromGroup : Incoming() { public companion object Key : AbstractPolymorphicMessageKey(Incoming, { it.safeCast() }) @@ -428,7 +457,8 @@ public abstract class OfflineMessageSource : MessageSource() { public enum class MessageSourceKind { GROUP, FRIEND, - TEMP + TEMP, + STRANGER } public val MessageSource.kind: MessageSourceKind @@ -442,6 +472,7 @@ public val OnlineMessageSource.kind: MessageSourceKind isAboutGroup() -> MessageSourceKind.GROUP isAboutFriend() -> MessageSourceKind.FRIEND isAboutTemp() -> MessageSourceKind.TEMP + isAboutStranger() -> MessageSourceKind.STRANGER else -> error("Internal error: OnlineMessageSource.kind reached an unexpected clause") } @@ -511,4 +542,4 @@ public inline val MessageChain.source: MessageSource */ @get:JvmSynthetic public inline val MessageChain.sourceOrNull: MessageSource? - get() = this[MessageSource] \ No newline at end of file + get() = this[MessageSource] diff --git a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt index c1ae9e1a7..27f5f7314 100644 --- a/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt +++ b/mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt @@ -15,10 +15,7 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai -import net.mamoe.mirai.contact.ContactOrBot -import net.mamoe.mirai.contact.Friend -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.MessageSource.Key.isAboutFriend import net.mamoe.mirai.message.data.MessageSource.Key.isAboutGroup import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp @@ -238,6 +235,7 @@ internal class MessageSourceBuilderImpl : MessageSourceBuilder() { this is Member || target is Member -> MessageSourceKind.TEMP this is Bot && target is Friend -> MessageSourceKind.FRIEND this is Friend && target is Bot -> MessageSourceKind.FRIEND + this is Stranger || target is Stranger -> MessageSourceKind.STRANGER else -> throw IllegalArgumentException("Cannot determine source kind for sender $this and target $target") } return this@MessageSourceBuilderImpl diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index 22d9df389..ec0c063b2 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -236,6 +236,15 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { ) } + @LowLevelApi + override fun _lowLevelNewStranger(bot: Bot, strangerInfo: StrangerInfo): Stranger { + return StrangerImpl( + bot.asQQAndroidBot(), + bot.coroutineContext + SupervisorJob(bot.supervisorJob), + strangerInfo + ) + } + @OptIn(LowLevelApi::class) override suspend fun _lowLevelQueryGroupList(bot: Bot): Sequence { @@ -308,7 +317,9 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { } } is MessageSourceFromFriendImpl, - is MessageSourceToFriendImpl + is MessageSourceToFriendImpl, + is MessageSourceFromStrangerImpl, + is MessageSourceToStrangerImpl, -> network.run { check(source.fromId == bot.id) { "can only recall a message sent by bot" @@ -339,7 +350,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { } is OfflineMessageSource -> network.run { when (source.kind) { - MessageSourceKind.FRIEND -> { + MessageSourceKind.FRIEND, MessageSourceKind.STRANGER -> { check(source.fromId == bot.id) { "can only recall a message sent by bot" } diff --git a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt index 0c571874f..e122d5724 100644 --- a/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt +++ b/mirai-core/src/commonMain/kotlin/QQAndroidBot.kt @@ -19,6 +19,7 @@ import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.* import net.mamoe.mirai.internal.contact.OtherClientImpl +import net.mamoe.mirai.internal.contact.StrangerInfoImpl import net.mamoe.mirai.internal.contact.checkIsGroupImpl import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.message.* @@ -116,6 +117,8 @@ internal class QQAndroidBot constructor( get() = client.wLoginSigInfo.sKey.data .fold(5381) { acc: Int, b: Byte -> acc + acc.shl(5) + b.toInt() } .and(Int.MAX_VALUE) + override val asStranger: Stranger by lazy { Mirai._lowLevelNewStranger(bot, StrangerInfoImpl(bot.id, bot.nick)) } + override val strangers: ContactList = ContactList() } internal val EMPTY_BYTE_ARRAY = ByteArray(0) diff --git a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt index 8c73523f9..4d869528f 100644 --- a/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt +++ b/mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt @@ -10,7 +10,11 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Friend +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.User +import net.mamoe.mirai.data.UserInfo import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.EventCancelledException @@ -29,14 +33,17 @@ import kotlin.coroutines.CoroutineContext import kotlin.math.roundToInt import kotlin.time.measureTime +internal open class UserInfoImpl(override val uin: Long, override val nick: String, override val remark: String = "") : + UserInfo + internal abstract class AbstractUser( bot: Bot, coroutineContext: CoroutineContext, - friendInfo: net.mamoe.mirai.data.FriendInfo, + userInfo: UserInfo, ) : User, AbstractContact(bot, coroutineContext) { - final override val id: Long = friendInfo.uin - final override var nick: String = friendInfo.nick - final override val remark: String = friendInfo.remark + final override val id: Long = userInfo.uin + final override var nick: String = userInfo.nick + final override val remark: String = userInfo.remark @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") override suspend fun uploadImage(resource: ExternalResource): Image { @@ -57,6 +64,12 @@ internal abstract class AbstractUser( ).sendAndExpect() } + val kind = when (this) { + is Stranger -> "stranger" + is Friend -> "friend" + is Member -> "temp" + else -> "unknown" + } return when (response) { is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(response.resourceId) .also { @@ -64,7 +77,7 @@ internal abstract class AbstractUser( } is LongConn.OffPicUp.Response.RequireUpload -> { bot.network.logger.verbose { - "[Http] Uploading friend image, size=${resource.size.sizeToString()}" + "[Http] Uploading $kind image, size=${resource.size.sizeToString()}" } val time = measureTime { @@ -78,7 +91,7 @@ internal abstract class AbstractUser( } bot.network.logger.verbose { - "[Http] Uploading friend image: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" + "[Http] Uploading $kind image: succeed at ${(resource.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" } /* diff --git a/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt b/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt index f8b90f5ec..f80514b42 100644 --- a/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/MemberInfoImpl.kt @@ -10,7 +10,6 @@ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.MemberPermission -import net.mamoe.mirai.data.FriendInfoImpl import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopMemberInfo @@ -23,7 +22,7 @@ internal class MemberInfoImpl( override val specialTitle: String, override val muteTimestamp: Int, override val anonymousId: String?, -) : MemberInfo, FriendInfoImpl(uin, nick, remark) { +) : MemberInfo, UserInfoImpl(uin, nick, remark) { constructor( jceInfo: StTroopMemberInfo, groupOwnerId: Long diff --git a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt index ce433b779..284d03792 100644 --- a/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt @@ -54,11 +54,16 @@ internal class NormalMemberImpl constructor( require(message.isContentNotEmpty()) { "message is empty" } val asFriend = this.asFriendOrNull() + val asStranger = this.asStrangerOrNull() return (asFriend?.sendMessageImpl( message, friendReceiptConstructor = { MessageReceipt(it, asFriend) }, tReceiptConstructor = { MessageReceipt(it, this) } + ) ?: asStranger?.sendMessageImpl( + message, + strangerReceiptConstructor = { MessageReceipt(it, asStranger) }, + tReceiptConstructor = { MessageReceipt(it, this) } ) ?: sendMessageImpl(message)).also { logMessageSent(message) } } diff --git a/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt new file mode 100644 index 000000000..4811faf23 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt @@ -0,0 +1,91 @@ +/* + * 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 + */ +@file:OptIn(LowLevelApi::class) +@file:Suppress( + "EXPERIMENTAL_API_USAGE", + "DEPRECATION_ERROR", + "NOTHING_TO_INLINE", + "INVISIBLE_MEMBER", + "INVISIBLE_REFERENCE" +) + +package net.mamoe.mirai.internal.contact + +import kotlinx.atomicfu.AtomicInt +import kotlinx.atomicfu.atomic +import net.mamoe.mirai.LowLevelApi +import net.mamoe.mirai.contact.Stranger +import net.mamoe.mirai.data.FriendInfoImpl +import net.mamoe.mirai.data.StrangerInfo +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.data.isContentNotEmpty +import network.protocol.packet.list.StrangerList +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract +import kotlin.coroutines.CoroutineContext + + +internal class StrangerInfoImpl( + override val uin: Long, override val nick: String, override val fromGroup: Long = 0, + override val remark: String = "" +) : StrangerInfo + +@OptIn(ExperimentalContracts::class) +internal inline fun StrangerInfo.checkIsInfoImpl(): FriendInfoImpl { + contract { + returns() implies (this@checkIsInfoImpl is StrangerInfoImpl) + } + check(this is FriendInfoImpl) { "A StrangerInfo instance is not instance of StrangerInfoImpl. Your instance: ${this::class.qualifiedName}" } + return this +} + +@OptIn(ExperimentalContracts::class) +internal inline fun Stranger.checkIsImpl(): StrangerImpl { + contract { + returns() implies (this@checkIsImpl is StrangerImpl) + } + check(this is StrangerImpl) { "A Stranger instance is not instance of StrangerImpl. Your instance: ${this::class.qualifiedName}" } + return this +} + +internal class StrangerImpl( + bot: QQAndroidBot, + coroutineContext: CoroutineContext, + internal val strangerInfo: StrangerInfo +) : Stranger, AbstractUser(bot, coroutineContext, strangerInfo) { + @Suppress("unused") // bug + val lastMessageSequence: AtomicInt = atomic(-1) + override suspend fun delete() { + check(bot.strangers[this.id] != null) { + "Stranger ${this.id} had already been deleted" + } + bot.network.run { + StrangerList.DelStranger(bot.client, this@StrangerImpl) + .sendAndExpect().also { + check(it.isSuccess) { "delete Stranger failed: ${it.result}" } + } + } + } + + @Suppress("DuplicatedCode") + override suspend fun sendMessage(message: Message): MessageReceipt { + require(message.isContentNotEmpty()) { "message is empty" } + return sendMessageImpl( + message, + strangerReceiptConstructor = { MessageReceipt(it, this) }, + tReceiptConstructor = { MessageReceipt(it, this) } + ).also { + logMessageSent(message) + } + } + + override fun toString(): String = "Stranger($id)" +} diff --git a/mirai-core/src/commonMain/kotlin/contact/util.kt b/mirai-core/src/commonMain/kotlin/contact/util.kt index 7e37b6f6e..447a98f8a 100644 --- a/mirai-core/src/commonMain/kotlin/contact/util.kt +++ b/mirai-core/src/commonMain/kotlin/contact/util.kt @@ -17,9 +17,11 @@ import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.message.MessageSourceToFriendImpl +import net.mamoe.mirai.internal.message.MessageSourceToStrangerImpl import net.mamoe.mirai.internal.message.ensureSequenceIdAvailable import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToFriend +import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.createToStranger import net.mamoe.mirai.internal.utils.estimateLength import net.mamoe.mirai.message.* import net.mamoe.mirai.message.data.* @@ -85,6 +87,56 @@ internal suspend fun Friend.sendMessageImpl( return tReceiptConstructor(source) } +internal suspend fun Stranger.sendMessageImpl( + message: Message, + strangerReceiptConstructor: (MessageSourceToStrangerImpl) -> MessageReceipt, + tReceiptConstructor: (MessageSourceToStrangerImpl) -> MessageReceipt +): MessageReceipt { + contract { callsInPlace(strangerReceiptConstructor, InvocationKind.EXACTLY_ONCE) } + val bot = bot.asQQAndroidBot() + + val chain = kotlin.runCatching { + StrangerMessagePreSendEvent(this, message).broadcast() + }.onSuccess { + check(!it.isCancelled) { + throw EventCancelledException("cancelled by StrangerMessagePreSendEvent") + } + }.getOrElse { + throw EventCancelledException("exception thrown when broadcasting StrangerMessagePreSendEvent", it) + }.message.asMessageChain() + chain.verityLength(message, this, {}, {}) + + chain.firstIsInstanceOrNull()?.source?.ensureSequenceIdAvailable() + + lateinit var source: MessageSourceToStrangerImpl + val result = bot.network.runCatching { + MessageSvcPbSendMsg.createToStranger( + bot.client, + this@sendMessageImpl, + chain, + ) { + source = it + }.sendAndExpect().let { + check(it is MessageSvcPbSendMsg.Response.SUCCESS) { + "Send stranger message failed: $it" + } + } + strangerReceiptConstructor(source) + } + + result.fold( + onSuccess = { + StrangerMessagePostSendEvent(this, chain, null, it) + }, + onFailure = { + StrangerMessagePostSendEvent(this, chain, it, null) + } + ).broadcast() + + result.getOrThrow() + return tReceiptConstructor(source) +} + internal fun Contact.logMessageSent(message: Message) { if (message !is LongMessage) { bot.logger.verbose("$this <- $message".replaceMagicCodes()) @@ -128,6 +180,9 @@ internal fun net.mamoe.mirai.event.events.MessageEvent.logMessageReceived() { is net.mamoe.mirai.event.events.TempMessageEvent -> bot.logger.verbose { "[${group.name}(${group.id})] $senderName(Temp ${sender.id}) -> $message".replaceMagicCodes() } + is net.mamoe.mirai.event.events.StrangerMessageEvent -> bot.logger.verbose { + "[$senderName(Stranger ${sender.id}) -> $message".replaceMagicCodes() + } is net.mamoe.mirai.event.events.FriendMessageEvent -> bot.logger.verbose { "${sender.nick}(${sender.id}) -> $message".replaceMagicCodes() } diff --git a/mirai-core/src/commonMain/kotlin/message/conversions.kt b/mirai-core/src/commonMain/kotlin/message/conversions.kt index cbf6dff22..82f284971 100644 --- a/mirai-core/src/commonMain/kotlin/message/conversions.kt +++ b/mirai-core/src/commonMain/kotlin/message/conversions.kt @@ -238,27 +238,26 @@ private val PB_RESERVE_FOR_PTT = private val PB_RESERVE_FOR_DOUTU = "78 00 90 01 01 F8 01 00 A0 02 00 C8 02 00".hexToBytes() private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes() - internal fun MsgComm.Msg.toMessageChain( bot: Bot, groupIdOrZero: Long, onlineSource: Boolean, - isTemp: Boolean = false -): MessageChain = listOf(this).toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp) + messageSourceKind: MessageSourceKind +): MessageChain = listOf(this).toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, messageSourceKind) internal fun List.toMessageChain( bot: Bot, groupIdOrZero: Long, onlineSource: Boolean, - isTemp: Boolean = false -): MessageChain = map { it.msg }.toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, isTemp) + messageSourceKind: MessageSourceKind +): MessageChain = map { it.msg }.toMessageChain(bot, bot.id, groupIdOrZero, onlineSource, messageSourceKind) internal fun List.toMessageChain( bot: Bot?, botId: Long, groupIdOrZero: Long, onlineSource: Boolean, - isTemp: Boolean = false + messageSourceKind: MessageSourceKind ): MessageChain { val elements = this.flatMap { it.msgBody.richText.elems } @@ -277,13 +276,15 @@ internal fun List.toMessageChain( return buildMessageChain(elements.size + 1 + ptts.size) { if (onlineSource) { checkNotNull(bot) { "bot is null" } - when { - isTemp -> +MessageSourceFromTempImpl(bot, this@toMessageChain) - groupIdOrZero != 0L -> +MessageSourceFromGroupImpl(bot, this@toMessageChain) - else -> +MessageSourceFromFriendImpl(bot, this@toMessageChain) + + when (messageSourceKind) { + MessageSourceKind.TEMP -> +MessageSourceFromTempImpl(bot, this@toMessageChain) + MessageSourceKind.GROUP -> +MessageSourceFromGroupImpl(bot, this@toMessageChain) + MessageSourceKind.FRIEND -> +MessageSourceFromFriendImpl(bot, this@toMessageChain) + MessageSourceKind.STRANGER -> +MessageSourceFromStrangerImpl(bot, this@toMessageChain) } } else { - +OfflineMessageSourceImplByMsg(this@toMessageChain, botId) + +OfflineMessageSourceImplByMsg(bot, this@toMessageChain, botId) } elements.joinToMessageChain(groupIdOrZero, botId, this) addAll(ptts) diff --git a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt index 3d1c903ac..c9cea71be 100644 --- a/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/incomingSourceImpl.kt @@ -16,6 +16,7 @@ import kotlinx.serialization.Transient import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.newAnonymous import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody @@ -25,10 +26,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.MessageSourceSerializerImpl -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.message.data.* import net.mamoe.mirai.utils.encodeToBase64 import net.mamoe.mirai.utils.encodeToString import net.mamoe.mirai.utils.mapToIntArray @@ -85,15 +83,51 @@ internal class MessageSourceFromFriendImpl( it.msgBody.richText.attr?.random ?: 0 } // other client 消息的这个是0 override val time: Int get() = msg.first().msgHead.msgTime - override val originalMessage: MessageChain by lazy { msg.toMessageChain(bot, bot.id, 0, false) } + override val originalMessage: MessageChain by lazy { + msg.toMessageChain( + bot, + bot.id, + 0, + false, + MessageSourceKind.FRIEND + ) + } override val sender: Friend get() = bot.getFriendOrFail(msg.first().msgHead.fromUin) - private val jceData by lazy { msg.toJceDataFriendOrTemp(internalIds) } + private val jceData by lazy { msg.toJceDataPrivate(internalIds) } override fun toJceData(): ImMsgBody.SourceMsg = jceData } -private fun List.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg { +internal class MessageSourceFromStrangerImpl( + override val bot: Bot, + val msg: List +) : OnlineMessageSource.Incoming.FromStranger(), MessageSourceInternal { + 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() = msg.mapToIntArray { + it.msgBody.richText.attr?.random ?: 0 + } // other client 消息的这个是0 + override val time: Int get() = msg.first().msgHead.msgTime + override val originalMessage: MessageChain by lazy { + msg.toMessageChain( + bot, + bot.id, + 0, + false, + MessageSourceKind.STRANGER + ) + } + override val sender: Stranger get() = bot.getStrangerOrFail(msg.first().msgHead.fromUin) + + private val jceData by lazy { msg.toJceDataPrivate(internalIds) } + + override fun toJceData(): ImMsgBody.SourceMsg = jceData +} + +private fun List.toJceDataPrivate(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())) } @@ -144,7 +178,7 @@ internal class MessageSourceFromTempImpl( bot.id, groupIdOrZero = 0, onlineSource = false, - isTemp = false, + MessageSourceKind.TEMP ) } override val sender: Member @@ -152,7 +186,7 @@ internal class MessageSourceFromTempImpl( bot.getGroupOrFail(c2cTmpMsgHead!!.groupUin).getOrFail(fromUin) } - private val jceData by lazy { msg.toJceDataFriendOrTemp(internalIds) } + private val jceData by lazy { msg.toJceDataPrivate(internalIds) } override fun toJceData(): ImMsgBody.SourceMsg = jceData } @@ -170,7 +204,7 @@ internal data class MessageSourceFromGroupImpl( override val ids: IntArray get() = sequenceIds override val time: Int get() = msg.first().msgHead.msgTime override val originalMessage: MessageChain by lazy { - msg.toMessageChain(bot, bot.id, groupIdOrZero = group.id, onlineSource = false) + msg.toMessageChain(bot, bot.id, groupIdOrZero = group.id, onlineSource = false, MessageSourceKind.GROUP) } override val sender: Member by lazy { diff --git a/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt index 081118dfc..87e5d1116 100644 --- a/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/offlineSourceImpl.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai.internal.message import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import net.mamoe.mirai.Bot 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.SourceMsg @@ -91,11 +92,25 @@ internal data class OfflineMessageSourceImplData( internal class OfflineMessageSourceImplByMsg( // from other sources' originalMessage + bot: Bot?, val delegate: List, override val botId: Long, ) : OfflineMessageSource(), MessageSourceInternal { override val kind: MessageSourceKind = - if (delegate.first().msgHead.groupInfo != null) MessageSourceKind.GROUP else MessageSourceKind.FRIEND + when { + delegate.first().msgHead.groupInfo != null -> { + MessageSourceKind.GROUP + } + delegate.first().msgHead.c2cTmpMsgHead != null -> { + MessageSourceKind.TEMP + } + bot?.getStranger(delegate.first().msgHead.fromUin) != null -> { + MessageSourceKind.STRANGER + } + else -> { + MessageSourceKind.FRIEND + } + } override val ids: IntArray get() = sequenceIds override val internalIds: IntArray = delegate.mapToIntArray { it.msgHead.msgUid.toInt() } override val time: Int @@ -110,7 +125,7 @@ internal class OfflineMessageSourceImplByMsg( botId, groupIdOrZero = delegate.first().msgHead.groupInfo?.groupCode ?: 0, onlineSource = false, - isTemp = delegate.first().msgHead.c2cTmpMsgHead != null + messageSourceKind = kind ) } override val sequenceIds: IntArray = delegate.mapToIntArray { it.msgHead.msgSeq } diff --git a/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt b/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt index cc7ed23f4..cc0e37395 100644 --- a/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt +++ b/mirai-core/src/commonMain/kotlin/message/outgoingSourceImpl.kt @@ -16,10 +16,7 @@ import kotlinx.coroutines.Deferred import kotlinx.coroutines.ExperimentalCoroutinesApi import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai -import net.mamoe.mirai.contact.ContactOrBot -import net.mamoe.mirai.contact.Friend -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.asyncFromEventOrNull import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm @@ -92,6 +89,23 @@ internal class MessageSourceToFriendImpl( override fun toJceData(): ImMsgBody.SourceMsg = jceData } +internal class MessageSourceToStrangerImpl( + override val sequenceIds: IntArray, + override val internalIds: IntArray, + override val time: Int, + override val originalMessage: MessageChain, + override val sender: Bot, + override val target: Stranger +) : OnlineMessageSource.Outgoing.ToStranger(), MessageSourceInternal { + override val bot: Bot + get() = sender + override val ids: IntArray + get() = sequenceIds + override var isRecalledOrPlanned: AtomicBoolean = AtomicBoolean(false) + private val jceData by lazy { toJceDataImpl(subject) } + override fun toJceData(): ImMsgBody.SourceMsg = jceData +} + internal class MessageSourceToTempImpl( override val sequenceIds: IntArray, override val internalIds: IntArray, diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt index 5d0a0c8bb..8680d2edc 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidBotNetworkHandler.kt @@ -44,6 +44,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.network.* import net.mamoe.mirai.utils.* +import network.protocol.packet.list.StrangerList import java.util.concurrent.ConcurrentLinkedQueue import kotlin.coroutines.CoroutineContext @@ -292,6 +293,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo private var initFriendOk = false private var initGroupOk = false + private var initStrangerOk = false /** * Don't use concurrently @@ -352,6 +354,28 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo }.getOrThrow() } + suspend fun reloadStrangerList() { + if (initStrangerOk) { + return + } + var currentCount = 0 + logger.info { "Start loading stranger list..." } + val response = StrangerList.GetStrangerList(bot.client) + .sendAndExpect(timeoutMillis = 5000, retry = 2) + + if (response.result == 0) { + response.strangerList.forEach { + // atomic + bot.strangers.delegate.add( + StrangerImpl(bot, bot.coroutineContext, StrangerInfoImpl(it.uin, it.nick.decodeToString())) + ).also { currentCount++ } + } + } + logger.info { "Successfully loaded stranger list: $currentCount in total" } + initStrangerOk = true + + } + suspend fun reloadGroupList() { if (initGroupOk) { return @@ -386,6 +410,9 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo if (!initGroupOk) { bot.groups.delegate.removeAll { it.cancel(reInitCancellationException); true } } + if (!initStrangerOk) { + bot.strangers.delegate.removeAll { it.cancel(reInitCancellationException); true } + } } if (!pendingEnabled) { @@ -396,6 +423,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo coroutineScope { launch { reloadFriendList() } launch { reloadGroupList() } + launch { reloadStrangerList() } } this@QQAndroidBotNetworkHandler.launch(CoroutineName("Awaiting ConfigPushSvc.PushReq")) { diff --git a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt index ba0f3adfe..6ccb92f8a 100644 --- a/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt +++ b/mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt @@ -81,6 +81,7 @@ internal open class QQAndroidClient( val subAppId: Long get() = protocol.id + internal var strangerSeq: Int = 0 internal val serverList: MutableList> = DefaultServerList.toMutableList() val keys: Map by lazy { diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/FrdSysMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/FrdSysMsg.kt new file mode 100644 index 000000000..3360fba38 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/FrdSysMsg.kt @@ -0,0 +1,110 @@ +/* +* 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 +*/ + +@file:Suppress("unused", "SpellCheckingInspection") + +package net.mamoe.mirai.internal.network.protocol.data.proto + +import kotlinx.serialization.Serializable +import kotlinx.serialization.protobuf.ProtoNumber +import net.mamoe.mirai.internal.utils.io.ProtoBuf + +internal class FrdSysMsg { + @Serializable + internal class AddFrdSNInfo( + @JvmField @ProtoNumber(1) val notSeeDynamic: Int = 0, + @JvmField @ProtoNumber(2) val setSn: Int = 0 + ) : ProtoBuf + + @Serializable + internal class AddFriendVerifyInfo( + @JvmField @ProtoNumber(1) val type: Int = 0, + @JvmField @ProtoNumber(2) val url: String = "", + @JvmField @ProtoNumber(3) val verifyInfo: String = "" + ) : ProtoBuf + + @Serializable + internal class AddtionInfo( + @JvmField @ProtoNumber(1) val poke: Int = 0, + @JvmField @ProtoNumber(2) val format: Int = 0, + @JvmField @ProtoNumber(3) val entityCategory: String = "", + @JvmField @ProtoNumber(4) val entityName: String = "", + @JvmField @ProtoNumber(5) val entityUrl: String = "" + ) : ProtoBuf + + @Serializable + internal class DiscussInfo( + @JvmField @ProtoNumber(1) val discussUin: Long = 0L, + @JvmField @ProtoNumber(2) val discussName: String = "", + @JvmField @ProtoNumber(3) val discussNick: String = "" + ) : ProtoBuf + + @Serializable + internal class EimInfo( + @JvmField @ProtoNumber(1) val eimFuin: Long = 0L, + @JvmField @ProtoNumber(2) val eimId: String = "", + @JvmField @ProtoNumber(3) val eimTelno: String = "", + @JvmField @ProtoNumber(4) val groupId: Long = 0L + ) : ProtoBuf + + @Serializable + internal class FriendHelloInfo( + @JvmField @ProtoNumber(1) val sourceName: String = "" + ) : ProtoBuf + + @Serializable + internal class FriendMiscInfo( + @JvmField @ProtoNumber(1) val fromuinNick: String = "" + ) : ProtoBuf + + @Serializable + internal class FriendSysMsg( + @JvmField @ProtoNumber(11) val msgGroupExt: GroupInfoExt? = null, + @JvmField @ProtoNumber(12) val msgIntiteInfo: InviteInfo? = null, + @JvmField @ProtoNumber(13) val msgSchoolInfo: SchoolInfo? = null, + @JvmField @ProtoNumber(100) val doubtFlag: Int = 0 + ) : ProtoBuf + + @Serializable + internal class GroupInfo( + @JvmField @ProtoNumber(1) val groupUin: Long = 0L, + @JvmField @ProtoNumber(2) val groupName: String = "", + @JvmField @ProtoNumber(3) val groupNick: String = "" + ) : ProtoBuf + + @Serializable + internal class GroupInfoExt( + @JvmField @ProtoNumber(1) val notifyType: Int = 0, + @JvmField @ProtoNumber(2) val groupCode: Long = 0L, + @JvmField @ProtoNumber(3) val fromGroupadmlist: Int = 0 + ) : ProtoBuf + + @Serializable + internal class InviteInfo( + @JvmField @ProtoNumber(1) val recommendUin: Long = 0L + ) : ProtoBuf + + @Serializable + internal class MsgEncodeFlag( + @JvmField @ProtoNumber(1) val isUtf8: Int = 0 + ) : ProtoBuf + + @Serializable + internal class SchoolInfo( + @JvmField @ProtoNumber(1) val schoolId: String = "", + @JvmField @ProtoNumber(2) val schoolName: String = "" + ) : ProtoBuf + + @Serializable + internal class TongXunLuNickInfo( + @JvmField @ProtoNumber(1) val fromuin: Long = 0L, + @JvmField @ProtoNumber(2) val touin: Long = 0L, + @JvmField @ProtoNumber(3) val tongxunluNickname: String = "" + ) : ProtoBuf +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt index fced80a82..4bfa883cf 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt @@ -14,6 +14,94 @@ import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.internal.utils.io.ProtoBuf +internal class Oidb0x5d4 : ProtoBuf { + @Serializable + internal class DelResult( + @JvmField @ProtoNumber(1) val uin: Long = 0L, + @JvmField @ProtoNumber(2) val res: Int = 0 + ) : ProtoBuf + + @Serializable + internal class ReqBody( + @JvmField @ProtoNumber(1) val uinList: List = emptyList() + ) : ProtoBuf + + @Serializable + internal class RspBody( + @JvmField @ProtoNumber(1) val seq: Int = 0, + @JvmField @ProtoNumber(2) val result: List = emptyList() + ) : ProtoBuf +} + +internal class Oidb0x5d2 : ProtoBuf { + @Serializable + internal class FriendInfo( + @JvmField @ProtoNumber(1) val uin: Long = 0L, + @JvmField @ProtoNumber(2) val gender: Int = 0, + @JvmField @ProtoNumber(3) val age: Int = 0, + @JvmField @ProtoNumber(4) val group: Int = 0, + @JvmField @ProtoNumber(5) val login: Int = 0, + @JvmField @ProtoNumber(6) val remark: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + internal class FriendEntry( + @JvmField @ProtoNumber(1) val uin: Long = 0L, + @JvmField @ProtoNumber(2) val nick: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + internal class GroupInfo( + @JvmField @ProtoNumber(1) val id: Int = 0, + @JvmField @ProtoNumber(2) val name: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + internal class LoginInfo( + @JvmField @ProtoNumber(1) val id: Int = 0, + @JvmField @ProtoNumber(2) val name: ByteArray = EMPTY_BYTE_ARRAY + ) : ProtoBuf + + @Serializable + internal class ReqBody( + @JvmField @ProtoNumber(1) val subCmd: Int = 0, + @JvmField @ProtoNumber(2) val reqGetList: ReqGetList? = null, + @JvmField @ProtoNumber(3) val reqGetInfo: ReqGetInfo? = null + ) : ProtoBuf + + @Serializable + internal class ReqGetInfo( + @JvmField @ProtoNumber(1) val uinList: List = emptyList() + ) : ProtoBuf + + @Serializable + internal class ReqGetList( + @JvmField @ProtoNumber(1) val seq: Int = 0 + ) : ProtoBuf + + @Serializable + internal class RspBody( + @JvmField @ProtoNumber(1) val subCmd: Int = 0, + @JvmField @ProtoNumber(2) val rspGetList: RspGetList? = null, + @JvmField @ProtoNumber(3) val rspGetInfo: RspGetInfo? = null + ) : ProtoBuf + + @Serializable + internal class RspGetInfo( + @JvmField @ProtoNumber(1) val groupInfo: List = emptyList(), + @JvmField @ProtoNumber(2) val loginInfo: List = emptyList(), + @JvmField @ProtoNumber(3) val time: Int = 0, + @JvmField @ProtoNumber(4) val frdInfo: List = emptyList(), + @JvmField @ProtoNumber(5) val frdDelete: List = emptyList() + ) : ProtoBuf + + @Serializable + internal class RspGetList( + @JvmField @ProtoNumber(1) val seq: Int = 0, + @JvmField @ProtoNumber(2) val list: List = emptyList() + ) : ProtoBuf +} + @Serializable internal class Oidb0x8a0 : ProtoBuf { @Serializable 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 3152dea16..987fec03a 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt @@ -28,6 +28,7 @@ import net.mamoe.mirai.internal.network.readUShortLVByteArray import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.crypto.adjustToPublicKey import net.mamoe.mirai.utils.* +import network.protocol.packet.list.StrangerList internal sealed class PacketFactory { /** @@ -149,7 +150,9 @@ internal object KnownPacketFactories { MultiMsg.ApplyUp, NewContact.SystemMsgNewFriend, NewContact.SystemMsgNewGroup, - ProfileService.GroupMngReq + ProfileService.GroupMngReq, + StrangerList.GetStrangerList, + StrangerList.DelStranger ) object IncomingFactories : List> by mutableListOf( diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt index 3913fc64c..5dfdf038f 100644 --- a/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt @@ -19,6 +19,8 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.withLock import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact +import kotlinx.io.core.readUByte +import kotlinx.io.core.readUShort import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission @@ -27,6 +29,7 @@ import net.mamoe.mirai.contact.appId import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event +import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.* @@ -35,6 +38,7 @@ import net.mamoe.mirai.internal.message.toMessageChain import net.mamoe.mirai.internal.network.MultiPacket import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x7 @@ -49,6 +53,7 @@ import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf +import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.buildMessageChain import net.mamoe.mirai.utils.* @@ -353,7 +358,8 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot): Packet? { } */ - 166 -> { + //167 单向好友 + 166, 167 -> { if (msgHead.fromUin == bot.id) { loop@ while (true) { val instance = bot.client.getFriendSeq() @@ -365,25 +371,40 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot): Packet? { } return null } - val friend = bot.getFriend(msgHead.fromUin) ?: return null - friend.checkIsFriendImpl() - if (!bot.firstLoginSucceed) { return null } - friend.lastMessageSequence.loop { - return if (friend.lastMessageSequence.compareAndSet( - it, - msgHead.msgSeq - ) && contentHead?.autoReply != 1 - ) { - FriendMessageEvent( - friend, - toMessageChain(bot, groupIdOrZero = 0, onlineSource = true), - msgHead.msgTime - ) - } else null - } + bot.getFriend(msgHead.fromUin)?.let { friend -> + friend.checkIsFriendImpl() + friend.lastMessageSequence.loop { + return if (friend.lastMessageSequence.compareAndSet( + it, + msgHead.msgSeq + ) && contentHead?.autoReply != 1 + ) { + FriendMessageEvent( + friend, + toMessageChain(bot, groupIdOrZero = 0, onlineSource = true, MessageSourceKind.FRIEND), + msgHead.msgTime + ) + } else null + } + } ?: bot.getStranger(msgHead.fromUin)?.let { stranger -> + stranger.checkIsImpl() + stranger.lastMessageSequence.loop { + return if (stranger.lastMessageSequence.compareAndSet( + it, + msgHead.msgSeq + ) && contentHead?.autoReply != 1 + ) { + StrangerMessageEvent( + stranger, + toMessageChain(bot, groupIdOrZero = 0, onlineSource = true, MessageSourceKind.STRANGER), + msgHead.msgTime + ) + } else null + } + } ?: return null } 208 -> { // friend ptt @@ -448,7 +469,7 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot): Packet? { bot, groupIdOrZero = 0, onlineSource = true, - isTemp = true + MessageSourceKind.TEMP ), msgHead.msgTime ) @@ -468,12 +489,50 @@ internal suspend fun MsgComm.Msg.transform(bot: QQAndroidBot): Packet? { } return null } - 732 -> { // unknown // 前 4 byte 是群号 return null } + //陌生人添加信息 + 191 -> { + var fromGroup = 0L + var pbNick = "" + msgBody.msgContent.read { + readUByte()// version + discardExact(readUByte().toInt())//skip + readUShort()//source id + readUShort()//SourceSubID + discardExact(readUShort().toLong())//skip size + if (readUShort().toInt() != 0) {//hasExtraInfo + discardExact(readUShort().toInt())//mail address info, skip + } + discardExact(4 + readUShort().toInt())//skip + for (i in 1..readUByte().toInt()) {//pb size + val type = readUShort().toInt() + val pbArray = ByteArray(readUShort().toInt() and 0xFF) + readAvailable(pbArray) + when (type) { + 1000 -> pbArray.loadAs(FrdSysMsg.GroupInfo.serializer()).let { fromGroup = it.groupUin } + 1002 -> pbArray.loadAs(FrdSysMsg.FriendMiscInfo.serializer()).let { pbNick = it.fromuinNick } + else -> { + }//ignore + } + } + } + val nick = sequenceOf(msgHead.fromNick, msgHead.authNick, pbNick).filter { it.isNotEmpty() }.firstOrNull() + ?: return null + val id = sequenceOf(msgHead.fromUin, msgHead.authUin).filter { it != 0L }.firstOrNull() ?: return null//对方QQ + Mirai._lowLevelNewStranger(bot, StrangerInfoImpl(id, nick, fromGroup)).let { + bot.getStranger(id)?.let { previous -> + bot.strangers.remove(id) + StrangerRelationChangeEvent.Deleted(previous).broadcast() + } + bot.strangers.delegate.add(it) + + return StrangerAddEvent(it) + } + } // 732: 27 0B 60 E7 0C 01 3E 03 3F A2 5E 90 60 E2 00 01 44 71 47 90 00 00 02 58 // 732: 27 0B 60 E7 11 00 40 08 07 20 E7 C1 AD B8 02 5A 36 08 B4 E7 E0 F0 09 1A 1A 08 9C D4 16 10 F7 D2 D8 F5 05 18 D0 E2 85 F4 06 20 00 28 00 30 B4 E7 E0 F0 09 2A 0E 08 00 12 0A 08 9C D4 16 10 00 18 01 20 00 30 00 38 00 // 732: 27 0B 60 E7 11 00 33 08 07 20 E7 C1 AD B8 02 5A 29 08 EE 97 85 E9 01 1A 19 08 EE D6 16 10 FF F2 D8 F5 05 18 E9 E7 A3 05 20 00 28 00 30 EE 97 85 E9 01 2A 02 08 00 30 00 38 00 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 9993b5ceb..e22ec36cb 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 @@ -14,13 +14,11 @@ import kotlinx.io.core.toByteArray import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.groupCode import net.mamoe.mirai.internal.contact.uin -import net.mamoe.mirai.internal.message.MessageSourceToFriendImpl -import net.mamoe.mirai.internal.message.MessageSourceToGroupImpl -import net.mamoe.mirai.internal.message.MessageSourceToTempImpl -import net.mamoe.mirai.internal.message.toRichTextElems +import net.mamoe.mirai.internal.message.* import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.QQAndroidClient.MessageSvcSyncData.PendingGroupMessageReceiptSyncId @@ -137,6 +135,36 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory Unit +): OutgoingPacket { + contract { + callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) + } + val source = MessageSourceToStrangerImpl( + internalIds = intArrayOf(Random.nextInt().absoluteValue), + sender = client.bot, + target = stranger, + time = currentTimeSeconds().toInt(), + sequenceIds = intArrayOf(client.atomicNextMessageSequenceId()), + originalMessage = message + ) + sourceCallback(source) + return createToStrangerImpl( + client, + stranger, + message, + source + ) +} + internal inline fun MessageSvcPbSendMsg.createToFriend( client: QQAndroidClient, qq: Friend, 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 84004f887..4a8a2d152 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 @@ -35,6 +35,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.utils._miraiContentToString import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf +import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.utils.* /** @@ -106,7 +107,12 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory("Onlin if (isFromSelfAccount) { return GroupMessageSyncEvent( - message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true), + message = msgs.toMessageChain( + bot, + groupIdOrZero = group.id, + onlineSource = true, + MessageSourceKind.GROUP + ), time = msgHead.msgTime, group = group, sender = sender, @@ -119,7 +125,12 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory("Onlin return GroupMessageEvent( senderName = name, sender = sender, - message = msgs.toMessageChain(bot, groupIdOrZero = group.id, onlineSource = true), + message = msgs.toMessageChain( + bot, + groupIdOrZero = group.id, + onlineSource = true, + MessageSourceKind.GROUP + ), permission = findMemberPermission(extraInfo?.flags ?: 0, sender, bot), time = msgHead.msgTime ) 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 15db191bf..2fca31b45 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 @@ -22,12 +22,9 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.Mirai -import net.mamoe.mirai.contact.Friend -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.contact.NormalMember -import net.mamoe.mirai.data.FriendInfo -import net.mamoe.mirai.data.GroupHonorType +import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.FriendInfoImpl +import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.* @@ -38,11 +35,9 @@ import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.jce.OnlinePushPack import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket -import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x115 -import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x122 +import net.mamoe.mirai.internal.network.protocol.data.proto.* import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.* import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44.Submsgtype0x44 -import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0xb3 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 @@ -488,7 +483,10 @@ internal object Transformers528 : Map by mapOf( ) ) bot.friends.delegate.add(new) - return@lambda528 sequenceOf(FriendAddEvent(new)) + return@lambda528 bot.getStranger(new.id)?.let { + bot.strangers.remove(new.id) + sequenceOf(StrangerRelationChangeEvent.Friended(it, new), FriendAddEvent(new)) + } ?: sequenceOf(FriendAddEvent(new)) }, 0xE2L to lambda528 { _ -> // TODO: unknown. maybe messages. @@ -532,38 +530,66 @@ internal object Transformers528 : Map by mapOf( return@lambda528 emptySequence() }, //戳一戳信息等 - 0x122L to lambda528 { bot, _ -> + 0x122L to lambda528 { bot, msgInfo -> val body = vProtobuf.loadAs(Submsgtype0x122.Submsgtype0x122.MsgBody.serializer()) when (body.templId) { //戳一戳 1132L, 1133L, 1134L, 1135L, 1136L, 10043L -> { //预置数据,服务器将不会提供己方已知消息 - var from: Friend = bot.asFriend + var from: User? = null var action = "" - var target: Friend = bot.asFriend + var target: User? = null 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()) ?: return@lambda528 emptySequence() - "uin_str2" -> target = bot.getFriend(value.toLong()) ?: return@lambda528 emptySequence() + "uin_str1" -> from = bot.getFriend(value.toLong()) ?: bot.getStranger(value.toLong()) + ?: return@lambda528 emptySequence() + "uin_str2" -> target = bot.getFriend(value.toLong()) ?: bot.getStranger(value.toLong()) + ?: return@lambda528 emptySequence() "suffix_str" -> suffix = value } } - - return@lambda528 sequenceOf( - if (target.id == bot.id) { - if (from.id == bot.id) - BotNudgedEvent.InPrivateSession.ByBot(target, action, suffix) - else - BotNudgedEvent.InPrivateSession.ByFriend(target, action, suffix) - } else { - if (from.id == bot.id) - FriendNudgedEvent.NudgedByBot(action, suffix, target) - else - FriendNudgedEvent.NudgedByHimself(action, suffix, target) + val subject: User = bot.getFriend(msgInfo.lFromUin) ?: bot.getStranger(msgInfo.lFromUin) + ?: return@lambda528 emptySequence() + //机器人自己戳自己 + if ((target == null && from == null) || (target?.id == from?.id && from?.id == bot.id)) { + sequenceOf(BotNudgedEvent.InPrivateSession.ByBot(subject, action, suffix)) + } else sequenceOf( + when (subject) { + is Friend -> when { + //机器人自身为目标 + target == null || target!!.id == bot.id -> BotNudgedEvent.InPrivateSession.ByFriend( + subject, + action, + suffix + ) + //机器人自身为发起者 + from == null || from!!.id == bot.id -> FriendNudgedEvent.NudgedByBot( + subject, + action, + suffix + ) + else -> FriendNudgedEvent.NudgedByHimself(subject, action, suffix) + } + is Stranger -> when { + //机器人自身为目标 + target == null || target!!.id == bot.id -> BotNudgedEvent.InPrivateSession.ByStranger( + subject, + action, + suffix + ) + //机器人自身为发起者 + from == null || from!!.id == bot.id -> StrangerNudgedEvent.NudgedByBot( + subject, + action, + suffix + ) + else -> StrangerNudgedEvent.NudgedByHimself(subject, action, suffix) + } + else -> error("Internal Error: Unable to find nudge type") } ) } @@ -602,6 +628,7 @@ internal object Transformers528 : Map by mapOf( fun DelFriend.transform(bot: QQAndroidBot): Sequence { return this.uint64Uins.asSequence().mapNotNull { + val friend = bot.getFriend(it) ?: return@mapNotNull null if (bot.friends.delegate.remove(friend)) { FriendDeleteEvent(friend) @@ -727,7 +754,6 @@ internal object Transformers528 : Map by mapOf( val from = info.nick when (info) { is FriendInfoImpl -> info.nick = to - is MemberInfoImpl -> info.nick = to else -> { bot.network.logger.debug { "Unknown how to update nick for $info" diff --git a/mirai-core/src/commonMain/kotlin/network/protocol/packet/list/StrangerList.kt b/mirai-core/src/commonMain/kotlin/network/protocol/packet/list/StrangerList.kt new file mode 100644 index 000000000..245b8df3e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/network/protocol/packet/list/StrangerList.kt @@ -0,0 +1,106 @@ +package network.protocol.packet.list; + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.contact.Stranger +import net.mamoe.mirai.event.broadcast +import net.mamoe.mirai.event.events.StrangerRelationChangeEvent +import net.mamoe.mirai.internal.QQAndroidBot +import net.mamoe.mirai.internal.network.Packet +import net.mamoe.mirai.internal.network.QQAndroidClient +import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x5d2 +import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x5d4 +import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.internal.utils._miraiContentToString +import net.mamoe.mirai.internal.utils.io.serialization.loadAs +import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf +import net.mamoe.mirai.internal.utils.io.serialization.toByteArray +import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf + +internal class StrangerList { + object GetStrangerList : OutgoingPacketFactory("OidbSvc.0x5d2_0") { + + class Response(val result: Int, val strangerList: List) : Packet + + operator fun invoke( + client: QQAndroidClient, + ): OutgoingPacket { + return buildOutgoingUniPacket(client) { + writeProtoBuf( + OidbSso.OIDBSSOPkg.serializer(), + OidbSso.OIDBSSOPkg( + command = 1490, + serviceType = 0, + bodybuffer = Oidb0x5d2.ReqBody( + subCmd = 1, + reqGetList = Oidb0x5d2.ReqGetList( + seq = client.strangerSeq + ) + ).toByteArray(Oidb0x5d2.ReqBody.serializer()) + ) + ) + } + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> + if (pkg.result == 0) { + pkg.bodybuffer.loadAs(Oidb0x5d2.RspBody.serializer()).also { + pkg._miraiContentToString() + }.rspGetList!!.let { + bot.client.strangerSeq = it.seq + return Response(pkg.result, it.list) + } + } + return Response(pkg.result, emptyList()) + } + } + + } + + object DelStranger : OutgoingPacketFactory("OidbSvc.0x5d4_0") { + class Response(val result: Int) : Packet { + val isSuccess = result == 0 + override fun toString(): String = "DelStranger.Response(success=${result == 0})" + } + + operator fun invoke( + client: QQAndroidClient, + stranger: Stranger + ): OutgoingPacket { + return buildOutgoingUniPacket(client) { + writeProtoBuf( + OidbSso.OIDBSSOPkg.serializer(), + OidbSso.OIDBSSOPkg( + command = 1492, + serviceType = 0, + result = 0, + bodybuffer = Oidb0x5d4.ReqBody( + uinList = listOf(stranger.id) + ).toByteArray(Oidb0x5d4.ReqBody.serializer()) + ) + ) + } + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> + if (pkg.result == 0) { + pkg.bodybuffer.loadAs(Oidb0x5d4.RspBody.serializer()).also { + pkg._miraiContentToString() + }.result.forEach { delResult -> + bot.getStranger(delResult.uin)?.let { + bot.strangers.remove(delResult.uin) + StrangerRelationChangeEvent.Deleted(it).broadcast() + } + } + } + return Response(pkg.result) + } + } + } + + +}