mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-08 17:20:11 +08:00
Group: new design
This commit is contained in:
parent
0c49d8c923
commit
b5efba2949
@ -7,7 +7,7 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import net.mamoe.mirai.Bot.ContactSystem
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.contact.internal.GroupImpl
|
||||
import net.mamoe.mirai.contact.internal.Group
|
||||
import net.mamoe.mirai.contact.internal.QQImpl
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
|
||||
@ -139,7 +139,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope {
|
||||
suspend fun getGroup(id: GroupId): Group = id.value.let {
|
||||
if (_groups.containsKey(it)) _groups[it]!!
|
||||
else groupsLock.withLock {
|
||||
_groups.getOrPut(it) { GroupImpl(bot, id) }
|
||||
_groups.getOrPut(it) { Group(bot, id) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.GroupInfo
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.QuiteGroupResponse
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.QuitGroupResponse
|
||||
import net.mamoe.mirai.utils.internal.PositiveNumbers
|
||||
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
|
||||
|
||||
@ -16,35 +16,65 @@ import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
|
||||
* - Group ID([Group.internalId]) 是与调用 API 时使用的 id.(在 QQ 客户端中不可见)
|
||||
* @author Him188moe
|
||||
*/
|
||||
interface Group : Contact {
|
||||
interface Group : Contact, Iterable<Member> {
|
||||
/**
|
||||
* 内部 ID. 内部 ID 为 [GroupId] 的映射
|
||||
*/
|
||||
val internalId: GroupInternalId
|
||||
|
||||
/**
|
||||
* 在 [Group] 实例创建的时候查询一次. 收到各事件后
|
||||
* 群主 (同步事件更新)
|
||||
* 进行 [updateGroupInfo] 时将会更新这个值.
|
||||
*/
|
||||
val member: ContactList<Member>
|
||||
val owner: Member
|
||||
|
||||
/**
|
||||
* 群名称 (同步事件更新)
|
||||
* 进行 [updateGroupInfo] 时将会更新这个值.
|
||||
*/
|
||||
val name: String
|
||||
|
||||
/**
|
||||
* 入群公告, 没有时为空字符串. (同步事件更新)
|
||||
* 进行 [updateGroupInfo] 时将会更新这个值.
|
||||
*/
|
||||
val announcement: String
|
||||
|
||||
/**
|
||||
* 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新
|
||||
*
|
||||
* **注意**: 获得的列表仅为这一时刻的成员列表的镜像. 它将不会被更新
|
||||
*/
|
||||
val members: ContactList<Member>
|
||||
|
||||
|
||||
/**
|
||||
* 获取群成员. 若此 ID 的成员不存在, 则会抛出 [kotlin.NoSuchElementException]
|
||||
*/
|
||||
suspend fun getMember(id: UInt): Member
|
||||
|
||||
/**
|
||||
* 查询群资料
|
||||
*/ // should be `suspend val` if kotlin supports in the future
|
||||
suspend fun queryGroupInfo(): GroupInfo
|
||||
* 更新群资料. 群资料会与服务器事件同步事件更新, 一般情况下不需要手动更新.
|
||||
*
|
||||
* @return 这一时刻的群资料
|
||||
*/
|
||||
suspend fun updateGroupInfo(): GroupInfo
|
||||
|
||||
/**
|
||||
* 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败
|
||||
*
|
||||
* @see QuiteGroupResponse.isSuccess 判断是否成功
|
||||
* @see QuitGroupResponse.isSuccess 判断是否成功
|
||||
*/
|
||||
suspend fun quite(): QuiteGroupResponse
|
||||
suspend fun quit(): QuitGroupResponse
|
||||
|
||||
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
|
||||
}
|
||||
|
||||
/**
|
||||
* 一般的用户可见的 ID.
|
||||
* 在 TIM/QQ 客户端中所看到的的号码均是这个 ID.
|
||||
*
|
||||
* 注: 在引用群 ID 时, 应使用 [GroupId] 或 [GroupInternalId] 类型, 而不是 [UInt]
|
||||
* 注: 在引用群 ID 时, 只应使用 [GroupId] 或 [GroupInternalId] 类型 (内联类无性能损失), 而不能使用 [UInt].
|
||||
*
|
||||
* @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId]
|
||||
* @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId]
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
package net.mamoe.mirai.contact.internal
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.contact.data.Profile
|
||||
@ -26,32 +25,50 @@ internal sealed class ContactImpl : Contact {
|
||||
override suspend fun sendMessage(message: Message) = sendMessage(message.chain())
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 [Group]
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
@PublishedApi
|
||||
internal suspend fun Group(bot: Bot, groupId: GroupId): Group {
|
||||
val info: RawGroupInfo = try {
|
||||
bot.withSession { GroupPacket.QueryGroupInfo(qqAccount, groupId.toInternalId(), sessionKey).sendAndExpect() }
|
||||
} catch (e: Exception) {
|
||||
error("Cannot obtain group info for id ${groupId.value}")
|
||||
}
|
||||
return GroupImpl(bot, groupId).apply { this.info = info.parseBy(this) }
|
||||
}
|
||||
|
||||
@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter")
|
||||
internal data class GroupImpl internal constructor(override val bot: Bot, val groupId: GroupId) : ContactImpl(), Group {
|
||||
override val id: UInt get() = groupId.value
|
||||
override val internalId = GroupId(id).toInternalId()
|
||||
|
||||
private val _members: MutableContactList<Member> = MutableContactList()
|
||||
override val member: ContactList<Member> = ContactList(_members)
|
||||
private val membersLock: Mutex = Mutex()
|
||||
internal lateinit var info: GroupInfo
|
||||
override val owner: Member get() = info.owner
|
||||
override val name: String get() = info.name
|
||||
override val announcement: String get() = info.announcement
|
||||
override val members: ContactList<Member> get() = info.members
|
||||
|
||||
override suspend fun getMember(id: UInt): Member =
|
||||
if (_members.containsKey(id)) _members[id]!!
|
||||
if (members.containsKey(id)) members[id]!!
|
||||
else throw NoSuchElementException("No such member whose id is $id in group $id")
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain) {
|
||||
bot.sendPacket(GroupPacket.Message(bot.qqAccount, internalId, bot.sessionKey, message))
|
||||
}
|
||||
|
||||
override suspend fun queryGroupInfo(): GroupInfo = bot.withSession {
|
||||
GroupPacket.QueryGroupInfo(qqAccount, internalId, sessionKey).sendAndExpect()
|
||||
override suspend fun updateGroupInfo(): GroupInfo = bot.withSession {
|
||||
GroupPacket.QueryGroupInfo(qqAccount, internalId, sessionKey).sendAndExpect<RawGroupInfo>().parseBy(this@GroupImpl)
|
||||
}
|
||||
|
||||
override suspend fun quite(): QuiteGroupResponse = bot.withSession {
|
||||
GroupPacket.QuiteGroup(qqAccount, sessionKey, internalId).sendAndExpect()
|
||||
override suspend fun quit(): QuitGroupResponse = bot.withSession {
|
||||
GroupPacket.QuitGroup(qqAccount, sessionKey, internalId).sendAndExpect()
|
||||
}
|
||||
|
||||
override fun toString(): String = "Group(${this.id})"
|
||||
|
||||
override fun iterator(): Iterator<Member> = members.delegate.values.iterator()
|
||||
}
|
||||
|
||||
internal data class QQImpl internal constructor(override val bot: Bot, override val id: UInt) : ContactImpl(), QQ {
|
||||
@ -76,6 +93,7 @@ internal data class QQImpl internal constructor(override val bot: Bot, override
|
||||
/**
|
||||
* 群成员
|
||||
*/
|
||||
@PublishedApi
|
||||
internal data class MemberImpl(private val delegate: QQ, override val group: Group, override val permission: MemberPermission) : QQ by delegate, Member {
|
||||
override fun toString(): String = "Member(id=${this.id}, permission=$permission)"
|
||||
}
|
@ -5,8 +5,6 @@ package net.mamoe.mirai.message
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImagePacket
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.io.debugPrintln
|
||||
import net.mamoe.mirai.utils.io.toUHexString
|
||||
|
||||
|
||||
/**
|
||||
@ -40,7 +38,7 @@ class ImageId0x03 constructor(override inline val value: String, inline val uniq
|
||||
.replace("-", "")
|
||||
.chunked(2)
|
||||
.map { (it[0] + it[1].toString()).toUByte(16).toByte() }
|
||||
.toByteArray().also { check(it.size == 16); debugPrintln("image md5=" + it.toUHexString()); debugPrintln("imageId=$this") }
|
||||
.toByteArray().also { check(it.size == 16) }
|
||||
}
|
||||
|
||||
@Suppress("FunctionName", "NOTHING_TO_INLINE")
|
||||
|
@ -161,7 +161,7 @@ abstract class BotSessionBase(
|
||||
|
||||
|
||||
inline val BotSession.isOpen: Boolean get() = socket.isOpen
|
||||
inline val BotSession.qqAccount: UInt get() = bot.account.id
|
||||
inline val BotSession.qqAccount: UInt get() = bot.account.id // 为了与群和好友的 id 区别.
|
||||
|
||||
/**
|
||||
* 取得 [BotNetworkHandler] 的 sessionKey.
|
||||
|
@ -69,7 +69,7 @@ enum class KnownPacketId(override inline val value: UShort, override inline val
|
||||
inline HEARTBEAT(0x0058u, HeartbeatPacket),
|
||||
inline S_KEY(0x001Du, RequestSKeyPacket),
|
||||
inline ACCOUNT_INFO(0x005Cu, RequestAccountInfoPacket),
|
||||
inline SEND_GROUP_MESSAGE(0x0002u, GroupPacket),
|
||||
inline GROUP_PACKET(0x0002u, GroupPacket),
|
||||
inline SEND_FRIEND_MESSAGE(0x00CDu, SendFriendMessagePacket),
|
||||
inline CAN_ADD_FRIEND(0x00A7u, CanAddFriendPacket),
|
||||
inline ADD_FRIEND(0x00A8u, AddFriendPacket),
|
||||
|
@ -5,7 +5,6 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
|
||||
import kotlinx.io.core.*
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.contact.internal.MemberImpl
|
||||
import net.mamoe.mirai.getQQ
|
||||
import net.mamoe.mirai.message.MessageChain
|
||||
import net.mamoe.mirai.message.internal.toPacket
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
@ -18,18 +17,48 @@ import net.mamoe.mirai.withSession
|
||||
/**
|
||||
* 群资料
|
||||
*/
|
||||
data class GroupInfo(
|
||||
val group: Group,
|
||||
val owner: Member,
|
||||
@Suppress("MemberVisibilityCanBePrivate") // 将来使用
|
||||
class GroupInfo(
|
||||
internal var _group: Group,
|
||||
internal var _owner: Member,
|
||||
internal var _name: String,
|
||||
internal var _announcement: String,
|
||||
internal var _members: ContactList<Member>
|
||||
) {
|
||||
val group: Group get() = _group
|
||||
val owner: Member get() = _owner
|
||||
val name: String get() = _name
|
||||
val announcement: String get() = _announcement
|
||||
val members: ContactList<Member> get() = _members
|
||||
|
||||
override fun toString(): String = "GroupInfo(id=${group.id}, owner=$owner, name=$name, announcement=$announcement, members=${members.idContentString}"
|
||||
}
|
||||
|
||||
data class RawGroupInfo(
|
||||
val group: UInt,
|
||||
val owner: UInt,
|
||||
val name: String,
|
||||
val announcement: String,
|
||||
val members: ContactList<Member>
|
||||
) : GroupPacket.GroupPacketResponse
|
||||
/**
|
||||
* 不含群主
|
||||
*/
|
||||
val members: Map<UInt, MemberPermission>
|
||||
) : GroupPacket.GroupPacketResponse {
|
||||
suspend inline fun parseBy(group: Group): GroupInfo = group.bot.withSession {
|
||||
GroupInfo(
|
||||
group,
|
||||
MemberImpl(this@RawGroupInfo.owner.qq(), group, MemberPermission.OWNER),
|
||||
this@RawGroupInfo.name,
|
||||
this@RawGroupInfo.announcement,
|
||||
ContactList(this@RawGroupInfo.members.mapValuesTo(MutableContactList()) { MemberImpl(it.key.qq(), group, MemberPermission.OWNER) })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出群的返回
|
||||
*/
|
||||
inline class QuiteGroupResponse(private val _group: GroupInternalId?) : Packet, GroupPacket.GroupPacketResponse {
|
||||
inline class QuitGroupResponse(private val _group: GroupInternalId?) : Packet, GroupPacket.GroupPacketResponse {
|
||||
val group: GroupInternalId get() = _group ?: error("request failed")
|
||||
val isSuccess: Boolean get() = _group != null
|
||||
|
||||
@ -37,7 +66,7 @@ inline class QuiteGroupResponse(private val _group: GroupInternalId?) : Packet,
|
||||
}
|
||||
|
||||
@Suppress("FunctionName")
|
||||
@AnnotatedId(KnownPacketId.SEND_GROUP_MESSAGE)
|
||||
@AnnotatedId(KnownPacketId.GROUP_PACKET)
|
||||
object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
|
||||
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2 (21173)")
|
||||
fun Message(
|
||||
@ -67,7 +96,7 @@ object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
|
||||
* 退出群
|
||||
*/
|
||||
@PacketVersion(date = "2019.11.28", timVersion = "2.3.2 (21173)")
|
||||
fun QuiteGroup(
|
||||
fun QuitGroup(
|
||||
bot: UInt,
|
||||
sessionKey: SessionKey,
|
||||
group: GroupInternalId
|
||||
@ -84,7 +113,7 @@ object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
|
||||
bot: UInt,
|
||||
groupInternalId: GroupInternalId,
|
||||
sessionKey: SessionKey
|
||||
): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "QueryBulletin", headerSizeHint = 9) {
|
||||
): OutgoingPacket = buildSessionPacket(bot, sessionKey, name = "QueryGroupInfo", headerSizeHint = 9) {
|
||||
writeUByte(0x72u)
|
||||
writeGroup(groupInternalId)
|
||||
writeZero(4)
|
||||
@ -105,19 +134,19 @@ object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
|
||||
|
||||
0x09u -> {
|
||||
if (readByte().toInt() == 0) {
|
||||
QuiteGroupResponse(readUInt().groupInternalId())
|
||||
QuitGroupResponse(readUInt().groupInternalId())
|
||||
} else {
|
||||
QuiteGroupResponse(null)
|
||||
QuitGroupResponse(null)
|
||||
}
|
||||
}
|
||||
|
||||
0x72u -> {
|
||||
discardExact(1) // 00
|
||||
discardExact(4) // group internal id
|
||||
val group = readUInt().group() // group id
|
||||
val group = readUInt() // group id
|
||||
|
||||
discardExact(13) //00 00 00 03 01 01 00 04 01 00 80 01 40
|
||||
val owner = MemberImpl(readUInt().qq(), group, MemberPermission.OWNER)
|
||||
val owner = readUInt()
|
||||
discardExact(22)
|
||||
val groupName = readUByteLVString()
|
||||
|
||||
@ -134,12 +163,11 @@ object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
|
||||
|
||||
val stop = readUInt() // 标记读取群成员的结束
|
||||
discardExact(1) // 00
|
||||
val members: MutableContactList<Member> = MutableContactList()
|
||||
members[owner.id] = owner
|
||||
val members = mutableMapOf<UInt, MemberPermission>()
|
||||
do {
|
||||
val qq = readUInt()
|
||||
val status = readUShort().toInt() // 这个群成员的状态, 最后一 bit 为管理员权限. 这里面还包含其他状态
|
||||
if (qq == owner.id) {
|
||||
if (qq == owner) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -147,9 +175,9 @@ object GroupPacket : SessionPacketFactory<GroupPacket.GroupPacketResponse>() {
|
||||
1 -> MemberPermission.OPERATOR
|
||||
else -> MemberPermission.MEMBER
|
||||
}
|
||||
members[qq] = MemberImpl(handler.bot.getQQ(qq), group, permission)
|
||||
members[qq] = permission
|
||||
} while (qq != stop && remaining != 0L)
|
||||
return GroupInfo(group, owner, groupName, announcement, ContactList(members))
|
||||
return RawGroupInfo(group, owner, groupName, announcement, members)
|
||||
/*
|
||||
* 群 Mirai
|
||||
*
|
||||
|
@ -93,7 +93,8 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
|
||||
override fun toString(): String = "LoginResponse.CaptchaInit"
|
||||
}
|
||||
|
||||
data class Success(
|
||||
@Suppress("unused")
|
||||
class Success(
|
||||
val sessionResponseDecryptionKey: SessionResponseDecryptionKey,
|
||||
|
||||
val token38: IoBuffer,//56
|
||||
@ -103,7 +104,9 @@ object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse,
|
||||
val nickname: String,
|
||||
val age: Short,
|
||||
val gender: Gender
|
||||
) : LoginResponse()
|
||||
) : LoginResponse() {
|
||||
override fun toString(): String = "LoginResponse.Success"
|
||||
}
|
||||
|
||||
data class Failed(val result: LoginResult) : LoginResponse()
|
||||
}
|
||||
@ -222,6 +225,7 @@ inline class SessionResponseDecryptionKey(private val delegate: IoBuffer) : Decr
|
||||
override fun decrypt(input: ByteReadPacket): ByteReadPacket = input.decryptBy(delegate)
|
||||
|
||||
override fun toString(): String = "SessionResponseDecryptionKey"
|
||||
|
||||
companion object Type : DecrypterType<SessionResponseDecryptionKey>
|
||||
}
|
||||
|
||||
|
@ -59,10 +59,12 @@ object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyRespo
|
||||
}
|
||||
}
|
||||
|
||||
data class SessionKeyResponse(
|
||||
class SessionKeyResponse(
|
||||
val sessionKey: SessionKey,
|
||||
val tlv0105: ByteReadPacket? = null
|
||||
) : Packet
|
||||
) : Packet {
|
||||
override fun toString(): String = "SessionKeyResponse"
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): SessionKeyResponse {
|
||||
when (remaining) {
|
||||
|
@ -46,33 +46,19 @@ object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey
|
||||
}
|
||||
|
||||
sealed class TouchResponse : Packet {
|
||||
data class OK(
|
||||
class OK(
|
||||
var loginTime: Int,
|
||||
val loginIP: String,
|
||||
val token0825: ByteArray
|
||||
) : TouchResponse() {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is OK) return false
|
||||
|
||||
if (loginTime != other.loginTime) return false
|
||||
if (loginIP != other.loginIP) return false
|
||||
if (!token0825.contentEquals(other.token0825)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = loginTime
|
||||
result = 31 * result + loginIP.hashCode()
|
||||
result = 31 * result + token0825.contentHashCode()
|
||||
return result
|
||||
}
|
||||
override fun toString(): String = "TouchResponse.OK"
|
||||
}
|
||||
|
||||
data class Redirection(
|
||||
class Redirection(
|
||||
val serverIP: String? = null
|
||||
) : TouchResponse()
|
||||
) : TouchResponse() {
|
||||
override fun toString(): String = "TouchResponse.Redirection"
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TouchResponse {
|
||||
|
Loading…
Reference in New Issue
Block a user