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 <Him188@mamoe.net>

* Add @LowLevelApi

Co-authored-by: Him188 <Him188@mamoe.net>

* 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 <Him188@mamoe.net>
Co-authored-by: Karlatemp <karlatemp@vip.qq.com>
This commit is contained in:
sandtechnology 2021-01-01 21:39:45 +08:00 committed by GitHub
parent 6aa71daad2
commit 1117c14c7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1260 additions and 114 deletions

View File

@ -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<Stranger>
/**
* [对方 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<Friend>
/**
* [对方 QQ 号码][id] 获取一个好友对象, 在获取失败时返回 `null`.
* [id] [Bot.id] 相同时返回 [Bot.asFriend]

View File

@ -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)
/**
* 判断此成员是否为好友
*/

View File

@ -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<Stranger>
/**
* 删除并屏蔽该陌生人, 屏蔽后对方将无法发送临时会话消息
*
* @see StrangerRelationChangeEvent.Deleted 陌生人删除事件
*/
@JvmBlockingBridge
public suspend fun delete()
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Stranger> =
this.sendMessage(message.toPlainText())
/**
* 创建一个 "戳一戳" 消息
*
* @see Nudge.sendTo 发送这个戳一戳消息
*/
@MiraiExperimentalApi
public override fun nudge(): StrangerNudge = StrangerNudge(this)
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -258,6 +258,15 @@ public open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, R
@MessageDsl
public fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessageEvent }
/** 如果是陌生人发来的消息 */
@MessageDsl
public fun sentByStranger(onEvent: MessageListener<StrangerMessageEvent, R>): 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 }

View File

@ -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)"
}
}
}

View File

@ -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)"

View File

@ -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<Stranger>?
) : UserMessagePostSendEvent<Stranger>()
// 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<User, Contact>, 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] 的消息
*

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
*/

View File

@ -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()

View File

@ -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, ToStranger>(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, ToTemp>(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, FromStranger>(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, FromGroup>(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]
get() = this[MessageSource]

View File

@ -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

View File

@ -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<Long> {
@ -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"
}

View File

@ -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<Stranger> = ContactList()
}
internal val EMPTY_BYTE_ARRAY = ByteArray(0)

View File

@ -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<LongConn.OffPicUp.Response>()
}
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"
}
/*

View File

@ -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

View File

@ -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) }
}

View File

@ -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<StrangerList.DelStranger.Response>().also {
check(it.isSuccess) { "delete Stranger failed: ${it.result}" }
}
}
}
@Suppress("DuplicatedCode")
override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> {
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)"
}

View File

@ -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 <T : User> Friend.sendMessageImpl(
return tReceiptConstructor(source)
}
internal suspend fun <T : User> Stranger.sendMessageImpl(
message: Message,
strangerReceiptConstructor: (MessageSourceToStrangerImpl) -> MessageReceipt<Stranger>,
tReceiptConstructor: (MessageSourceToStrangerImpl) -> MessageReceipt<T>
): MessageReceipt<T> {
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<QuoteReply>()?.source?.ensureSequenceIdAvailable()
lateinit var source: MessageSourceToStrangerImpl
val result = bot.network.runCatching {
MessageSvcPbSendMsg.createToStranger(
bot.client,
this@sendMessageImpl,
chain,
) {
source = it
}.sendAndExpect<MessageSvcPbSendMsg.Response>().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()
}

View File

@ -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<MsgOnlinePush.PbPushMsg>.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<MsgComm.Msg>.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<MsgComm.Msg>.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)

View File

@ -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<MsgComm.Msg>.toJceDataFriendOrTemp(ids: IntArray): ImMsgBody.SourceMsg {
internal class MessageSourceFromStrangerImpl(
override val bot: Bot,
val msg: List<MsgComm.Msg>
) : 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<MsgComm.Msg>.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 {

View File

@ -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<MsgComm.Msg>,
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 }

View File

@ -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,

View File

@ -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<StrangerList.GetStrangerList.Response>(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")) {

View File

@ -81,6 +81,7 @@ internal open class QQAndroidClient(
val subAppId: Long
get() = protocol.id
internal var strangerSeq: Int = 0
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
val keys: Map<String, ByteArray> by lazy {

View File

@ -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
}

View File

@ -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<Long> = emptyList()
) : ProtoBuf
@Serializable
internal class RspBody(
@JvmField @ProtoNumber(1) val seq: Int = 0,
@JvmField @ProtoNumber(2) val result: List<DelResult> = 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<Long> = 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<GroupInfo> = emptyList(),
@JvmField @ProtoNumber(2) val loginInfo: List<LoginInfo> = emptyList(),
@JvmField @ProtoNumber(3) val time: Int = 0,
@JvmField @ProtoNumber(4) val frdInfo: List<FriendInfo> = emptyList(),
@JvmField @ProtoNumber(5) val frdDelete: List<Long> = emptyList()
) : ProtoBuf
@Serializable
internal class RspGetList(
@JvmField @ProtoNumber(1) val seq: Int = 0,
@JvmField @ProtoNumber(2) val list: List<FriendEntry> = emptyList()
) : ProtoBuf
}
@Serializable
internal class Oidb0x8a0 : ProtoBuf {
@Serializable

View File

@ -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<TPacket : Packet?> {
/**
@ -149,7 +150,9 @@ internal object KnownPacketFactories {
MultiMsg.ApplyUp,
NewContact.SystemMsgNewFriend,
NewContact.SystemMsgNewGroup,
ProfileService.GroupMngReq
ProfileService.GroupMngReq,
StrangerList.GetStrangerList,
StrangerList.DelStranger
)
object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf(

View File

@ -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

View File

@ -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<MessageSvcPbSendMsg.
return response
}
/**
* 发送陌生人消息
*/
@Suppress("FunctionName")
internal inline fun createToStrangerImpl(
client: QQAndroidClient,
target: Stranger,
message: MessageChain,
source: MessageSourceToStrangerImpl
): OutgoingPacket = buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())
///return@buildOutgoingUniPacket
writeProtoBuf(
MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq(
routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = target.uin)),
contentHead = MsgComm.ContentHead(pkgNum = 1),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = message.toRichTextElems(messageTarget = target, withGeneralFlags = true)
)
),
msgSeq = source.sequenceIds.single(),
msgRand = source.internalIds.single(),
syncCookie = client.syncingController.syncCookie ?: byteArrayOf()
// msgVia = 1
)
)
}
/**
* 发送好友消息
*/
@ -348,6 +376,32 @@ internal inline fun MessageSvcPbSendMsg.createToTemp(
)
}
internal inline fun MessageSvcPbSendMsg.createToStranger(
client: QQAndroidClient,
stranger: Stranger,
message: MessageChain,
crossinline sourceCallback: (MessageSourceToStrangerImpl) -> 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,

View File

@ -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<Packet?>("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<Packet?>("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
)

View File

@ -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<Long, Lambda528> 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<Long, Lambda528> 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<Long, Lambda528> by mapOf(
fun DelFriend.transform(bot: QQAndroidBot): Sequence<Packet> {
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<Long, Lambda528> 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"

View File

@ -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<GetStrangerList.Response>("OidbSvc.0x5d2_0") {
class Response(val result: Int, val strangerList: List<Oidb0x5d2.FriendEntry>) : 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<DelStranger.Response>("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)
}
}
}
}