From b797ef3cc1c17ee394e43e5230f264869ce15ec7 Mon Sep 17 00:00:00 2001 From: Him188 Date: Sun, 24 Nov 2019 17:29:05 +0800 Subject: [PATCH] Redesign Contacts: Use interfaces and hide internal implementations --- .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 33 +-- .../kotlin/net.mamoe.mirai/BotHelper.kt | 6 +- .../kotlin/net.mamoe.mirai/contact/Contact.kt | 202 ++---------------- .../kotlin/net.mamoe.mirai/contact/Group.kt | 57 +++++ .../kotlin/net.mamoe.mirai/contact/Member.kt | 31 +++ .../kotlin/net.mamoe.mirai/contact/QQ.kt | 33 +++ .../net.mamoe.mirai/contact/data/Profile.kt | 52 +++++ .../contact/internal/ContactImpl.kt | 87 ++++++++ .../kotlin/net.mamoe.mirai/message/Message.kt | 2 - .../protocol/tim/packet/action/Profile.kt | 4 +- .../tim/packet/event/EventPacketFactory.kt | 1 - .../tim/packet/event/MemberKickEvent.kt | 25 +++ .../tim/packet/login/PasswordSubmission.kt | 2 +- .../net.mamoe.mirai/utils/ExternalImage.kt | 2 + 14 files changed, 327 insertions(+), 210 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/data/Profile.kt create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberKickEvent.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 25b410fae..780d19bb7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -7,6 +7,8 @@ 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.QQImpl import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult @@ -95,27 +97,31 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope { * @see Bot.contacts */ inner class ContactSystem internal constructor() { - inline val bot: Bot get() = this@Bot + val bot: Bot get() = this@Bot - private val _groups = ContactList() - private lateinit var groupsUpdater: Job - val groups = ContactList() + @Suppress("PropertyName") + internal val _groups = MutableContactList() + internal lateinit var groupsUpdater: Job private val groupsLock = Mutex() - private val _qqs = ContactList() //todo 实现群列表和好友列表获取 - private lateinit var qqUpdaterJob: Job - val qqs: ContactList = _qqs + val groups: ContactList = ContactList(_groups) + + @Suppress("PropertyName") + internal val _qqs = MutableContactList() //todo 实现群列表和好友列表获取 + internal lateinit var qqUpdaterJob: Job private val qqsLock = Mutex() + val qqs: ContactList = ContactList(_qqs) + /** * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个. * * 注: 这个方法是线程安全的 */ suspend fun getQQ(id: UInt): QQ = - if (qqs.containsKey(id)) qqs[id]!! + if (_qqs.containsKey(id)) _qqs[id]!! else qqsLock.withLock { - qqs.getOrPut(id) { QQ(bot, id) } + _qqs.getOrPut(id) { QQImpl(bot, id) } } /** @@ -131,12 +137,11 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope { * 注: 这个方法是线程安全的 */ suspend fun getGroup(id: GroupId): Group = id.value.let { - if (groups.containsKey(it)) groups[it]!! + if (_groups.containsKey(it)) _groups[it]!! else groupsLock.withLock { - groups.getOrPut(it) { Group(bot, id) } + _groups.getOrPut(it) { GroupImpl(bot, id) } } } - } suspend inline fun Int.qq(): QQ = getQQ(this.coerceAtLeastOrFail(0).toUInt()) @@ -152,8 +157,8 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope { suspend fun close() { network.close() this.coroutineContext.cancelChildren() - contacts.groups.clear() - contacts.qqs.clear() + contacts._groups.clear() + contacts._qqs.clear() } companion object { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt index f6483c68d..98ef59a7f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt @@ -38,14 +38,12 @@ suspend inline fun Bot.getGroup(internalId: GroupInternalId): Group = this.conta /** * 取得机器人的群成员列表 */ -inline val Bot.groups: ContactList - get() = this.contacts.groups +inline val Bot.groups: ContactList get() = this.contacts.groups /** * 取得机器人的好友列表 */ -inline val Bot.qqs: ContactList - get() = this.contacts.qqs +inline val Bot.qqs: ContactList get() = this.contacts.qqs /** * 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index a7557e7be..38a628ba9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -1,48 +1,40 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate") +@file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.contact -import com.soywiz.klock.Date -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.Deferred import net.mamoe.mirai.Bot import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.singleChain import net.mamoe.mirai.network.BotSession -import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsPacket -import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsResponse -import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket -import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket -import net.mamoe.mirai.network.sessionKey -import net.mamoe.mirai.qqAccount -import net.mamoe.mirai.sendPacket -import net.mamoe.mirai.utils.SuspendLazy -import net.mamoe.mirai.utils.internal.PositiveNumbers -import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail import net.mamoe.mirai.withSession import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract -class ContactList : MutableMap by mutableMapOf() /** * 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group]. * - * @param bot 这个联系人所属 [Bot] - * @param id 可以是 QQ 号码或者群号码 [GroupId]. - * * @author Him188moe */ -sealed class Contact(val bot: Bot, val id: UInt) { +interface Contact { + /** + * 这个联系人所属 [Bot] + */ + val bot: Bot + + /** + * 可以是 QQ 号码或者群号码 [GroupId]. + */ + val id: UInt /** * 向这个对象发送消息. * * 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右. */ - abstract suspend fun sendMessage(message: MessageChain) + suspend fun sendMessage(message: MessageChain) //这两个方法应写为扩展函数, 但为方便 import 还是写在这里 @@ -51,62 +43,6 @@ sealed class Contact(val bot: Bot, val id: UInt) { suspend fun sendMessage(message: Message) = sendMessage(message.singleChain()) } -/** - * 一般的用户可见的 ID. - * 在 TIM/QQ 客户端中所看到的的号码均是这个 ID. - * - * 注: 在引用群 ID 时, 应使用 [GroupId] 或 [GroupInternalId] 类型, 而不是 [UInt] - * - * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] - * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] - */ -inline class GroupId(inline val value: UInt) - -/** - * 将 [this] 转为 [GroupId]. - */ -fun UInt.groupId(): GroupId = GroupId(this) - -/** - * 将无符号整数格式的 [Long] 转为 [GroupId]. - * - * 注: 在 Java 中常用 [Long] 来表示 [UInt] - */ -fun @receiver:PositiveNumbers Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0).toUInt()) - -/** - * 一些群 API 使用的 ID. 在使用时会特别注明 - * - * 注: 在引用群 ID 时, 应使用 [GroupId] 或 [GroupInternalId] 类型, 而不是 [UInt] - * - * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] - * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] - */ -inline class GroupInternalId(inline val value: UInt) - -/** - * 群. - * - * Group ID 与 Group Number 并不是同一个值. - * - Group Number([Group.id]) 是通常使用的群号码.(在 QQ 客户端中可见) - * - Group ID([Group.internalId]) 是与调用 API 时使用的 id.(在 QQ 客户端中不可见) - * @author Him188moe - */ -@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") -class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot, groupId.value) { - val internalId = GroupId(id).toInternalId() - val members: ContactList - get() = TODO("Implementing group members is less important") - - override suspend fun sendMessage(message: MessageChain) { - bot.sendPacket(SendGroupMessagePacket(bot.qqAccount, internalId, bot.sessionKey, message)) - } - - override fun toString(): String = "Group(${this.id})" - - companion object -} - /** * 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值. * 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync] @@ -120,117 +56,11 @@ inline fun Contact.withSession(block: BotSession.() -> R): R { } /** - * QQ 对象. - * 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot]. - * 它不能被直接构造. 任何时候都应从 [Bot.qq], [Bot.ContactSystem.getQQ], [BotSession.qq] 或事件中获取. - * - * 对于同一个 [Bot] 任何一个人的 [QQ] 实例都是单一的. - * - * A QQ instance helps you to receive event from or sendPacket event to. - * Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same. - * - * @author Him188moe + * 只读联系人列表 */ -open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) { - private var _profile: Profile? = null - private val _initialProfile by bot.network.SuspendLazy { updateProfile() } - - /** - * 用户资料. - */ - val profile: Deferred - get() = if (_profile == null) _initialProfile else CompletableDeferred(_profile!!) - - override suspend fun sendMessage(message: MessageChain) = - bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message)) - - /** - * 更新个人资料. - * 将会同步更新 property [profile] - */ - suspend fun updateProfile(): Profile = bot.withSession { - _profile = RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey) - .sendAndExpect { it.profile } - - return _profile!! - } - - override fun toString(): String = "QQ(${this.id})" -} - +class ContactList internal constructor(private val delegate: MutableContactList) : Map by delegate /** - * 群成员 + * 可修改联系人列表. 只会在内部使用. */ -class Member internal constructor(bot: Bot, id: UInt, val group: Group) : QQ(bot, id) { - init { - TODO("Group member implementation") - } - - override fun toString(): String = "Member(${this.id})" -} - -/** - * 群成员的权限 - */ -enum class MemberPermission { - /** - * 群主 - */ - OWNER, - /** - * 管理员 - */ - OPERATOR, - /** - * 一般群成员 - */ - MEMBER; -} - -/** - * 个人资料 - */ -@Suppress("PropertyName") -data class Profile( - val qq: UInt, - val nickname: String, - val englishName: String?, - val chineseName: String?, - val qAge: Int?, // q 龄 - val zipCode: String?, - val phone: String?, - val gender: Gender, - val birthday: Date?, - val personalStatement: String?,// 个人说明 - val school: String?, - val homepage: String?, - val email: String?, - val company: String? -) { - - override fun toString(): String = "Profile(qq=$qq, " + - "nickname=$nickname, " + - "gender=$gender, " + - (englishName?.let { "englishName=$englishName, " } ?: "") + - (chineseName?.let { "chineseName=$chineseName, " } ?: "") + - (qAge?.toString()?.let { "qAge=$qAge, " } ?: "") + - (zipCode?.let { "zipCode=$zipCode, " } ?: "") + - (phone?.let { "phone=$phone, " } ?: "") + - (birthday?.toString()?.let { "birthday=$birthday, " } ?: "") + - (personalStatement?.let { "personalStatement=$personalStatement, " } ?: "") + - (school?.let { "school=$school, " } ?: "") + - (homepage?.let { "homepage=$homepage, " } ?: "") + - (email?.let { "email=$email, " } ?: "") + - (company?.let { "company=$company," } ?: "") + - ")"// 最终会是 ", )", 但这并不影响什么. -} - -/** - * 性别 - */ -enum class Gender { - SECRET, - MALE, - FEMALE; -} \ No newline at end of file +internal class MutableContactList : MutableMap by mutableMapOf() \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt new file mode 100644 index 000000000..53b68784c --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -0,0 +1,57 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.contact + +import net.mamoe.mirai.utils.internal.PositiveNumbers +import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail + + +/** + * 群. + * + * Group ID 与 Group Number 并不是同一个值. + * - Group Number([Group.id]) 是通常使用的群号码.(在 QQ 客户端中可见) + * - Group ID([Group.internalId]) 是与调用 API 时使用的 id.(在 QQ 客户端中不可见) + * @author Him188moe + */ +interface Group : Contact { + val internalId: GroupInternalId + + val member: ContactList + + suspend fun getMember(id: UInt): Member +} + +/** + * 一般的用户可见的 ID. + * 在 TIM/QQ 客户端中所看到的的号码均是这个 ID. + * + * 注: 在引用群 ID 时, 应使用 [GroupId] 或 [GroupInternalId] 类型, 而不是 [UInt] + * + * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] + * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] + */ +inline class GroupId(inline val value: UInt) + +/** + * 将 [this] 转为 [GroupId]. + */ +fun UInt.groupId(): GroupId = GroupId(this) + +/** + * 将无符号整数格式的 [Long] 转为 [GroupId]. + * + * 注: 在 Java 中常用 [Long] 来表示 [UInt] + */ +fun @receiver:PositiveNumbers Long.groupId(): GroupId = + GroupId(this.coerceAtLeastOrFail(0).toUInt()) + +/** + * 一些群 API 使用的 ID. 在使用时会特别注明 + * + * 注: 在引用群 ID 时, 应使用 [GroupId] 或 [GroupInternalId] 类型, 而不是 [UInt] + * + * @see GroupInternalId.toId 由 [GroupInternalId] 转换为 [GroupId] + * @see GroupId.toInternalId 由 [GroupId] 转换为 [GroupInternalId] + */ +inline class GroupInternalId(inline val value: UInt) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt new file mode 100644 index 000000000..674f88092 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -0,0 +1,31 @@ +package net.mamoe.mirai.contact + +/** + * 群成员. + * + * 使用 [QQ.equals]. 因此同 ID 的群成员和 QQ 是 `==` 的 + */ +interface Member : QQ, Contact { + /** + * 所在的群 + */ + val group: Group +} + +/** + * 群成员的权限 + */ +enum class MemberPermission { + /** + * 群主 + */ + OWNER, + /** + * 管理员 + */ + OPERATOR, + /** + * 一般群成员 + */ + MEMBER; +} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt new file mode 100644 index 000000000..b910b720e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt @@ -0,0 +1,33 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.contact + +import kotlinx.coroutines.Deferred +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.data.Profile +import net.mamoe.mirai.network.BotSession + +/** + * QQ 对象. + * 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot]. + * 它不能被直接构造. 任何时候都应从 [Bot.qq], [Bot.ContactSystem.getQQ], [BotSession.qq] 或事件中获取. + * + * 对于同一个 [Bot] 任何一个人的 [QQ] 实例都是单一的. + * + * A QQ instance helps you to receive event from or sendPacket event to. + * Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same. + * + * @author Him188moe + */ +interface QQ : Contact { + /** + * 用户资料. + */ + val profile: Deferred + + /** + * 更新个人资料. + * 将会同步更新 property [profile] + */ + suspend fun updateProfile(): Profile +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/data/Profile.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/data/Profile.kt new file mode 100644 index 000000000..d8acb2e65 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/data/Profile.kt @@ -0,0 +1,52 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + +package net.mamoe.mirai.contact.data + +import com.soywiz.klock.Date + +/** + * 个人资料 + */ +@Suppress("PropertyName") +data class Profile( + val qq: UInt, + val nickname: String, + val englishName: String?, + val chineseName: String?, + val qAge: Int?, // q 龄 + val zipCode: String?, + val phone: String?, + val gender: Gender, + val birthday: Date?, + val personalStatement: String?,// 个人说明 + val school: String?, + val homepage: String?, + val email: String?, + val company: String? +) { + + override fun toString(): String = "Profile(qq=$qq, " + + "nickname=$nickname, " + + "gender=$gender, " + + (englishName?.let { "englishName=$englishName, " } ?: "") + + (chineseName?.let { "chineseName=$chineseName, " } ?: "") + + (qAge?.toString()?.let { "qAge=$qAge, " } ?: "") + + (zipCode?.let { "zipCode=$zipCode, " } ?: "") + + (phone?.let { "phone=$phone, " } ?: "") + + (birthday?.toString()?.let { "birthday=$birthday, " } ?: "") + + (personalStatement?.let { "personalStatement=$personalStatement, " } ?: "") + + (school?.let { "school=$school, " } ?: "") + + (homepage?.let { "homepage=$homepage, " } ?: "") + + (email?.let { "email=$email, " } ?: "") + + (company?.let { "company=$company," } ?: "") + + ")"// 最终会是 ", )", 但这并不影响什么. +} + +/** + * 性别 + */ +enum class Gender { + SECRET, + MALE, + FEMALE; +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt new file mode 100644 index 000000000..66562c1aa --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/internal/ContactImpl.kt @@ -0,0 +1,87 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.contact.internal + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import net.mamoe.mirai.* +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.contact.data.Profile +import net.mamoe.mirai.message.Message +import net.mamoe.mirai.message.MessageChain +import net.mamoe.mirai.message.singleChain +import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsPacket +import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsResponse +import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket +import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket +import net.mamoe.mirai.network.sessionKey +import net.mamoe.mirai.utils.SuspendLazy + +internal sealed class ContactImpl : Contact { + abstract override suspend fun sendMessage(message: MessageChain) + + //这两个方法应写为扩展函数, 但为方便 import 还是写在这里 + override suspend fun sendMessage(plain: String) = sendMessage(plain.singleChain()) + + override suspend fun sendMessage(message: Message) = sendMessage(message.singleChain()) +} + +@Suppress("MemberVisibilityCanBePrivate", "CanBeParameter") +internal 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 = MutableContactList() + override val member: ContactList = ContactList(_members) + private val membersLock: Mutex = Mutex() + + override suspend fun getMember(id: UInt): Member = + if (_members.containsKey(id)) _members[id]!! + else membersLock.withLock { + _members.getOrPut(id) { MemberImpl(bot, bot.getQQ(id), this) } + } + + override suspend fun sendMessage(message: MessageChain) { + bot.sendPacket(SendGroupMessagePacket(bot.qqAccount, internalId, bot.sessionKey, message)) + } + + override fun toString(): String = "Group(${this.id})" +} + +internal class QQImpl internal constructor(override val bot: Bot, override val id: UInt) : ContactImpl(), QQ { + private var _profile: Profile? = null + private val _initialProfile by bot.network.SuspendLazy { updateProfile() } + + override val profile: Deferred get() = if (_profile == null) _initialProfile else CompletableDeferred(_profile!!) + + override suspend fun sendMessage(message: MessageChain) = + bot.sendPacket(SendFriendMessagePacket(bot.qqAccount, id, bot.sessionKey, message)) + + /** + * 更新个人资料. + * 将会同步更新 property [profile] + */ + override suspend fun updateProfile(): Profile = bot.withSession { + _profile = RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey) + .sendAndExpect { it.profile } + + return _profile!! + } + + override fun toString(): String = "QQ(${this.id})" +} + +/** + * 群成员 + */ +internal class MemberImpl(override val bot: Bot, private val delegate: QQ, override val group: Group) : Member, ContactImpl() { + override val profile: Deferred get() = delegate.profile + override val id: UInt get() = delegate.id + + override suspend fun updateProfile(): Profile = delegate.updateProfile() + override suspend fun sendMessage(message: MessageChain) = delegate.sendMessage(message) + + override fun toString(): String = "Member(${this.id})" +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt index 74d0581a2..e42649477 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt @@ -170,8 +170,6 @@ inline class ImageId0x06(override inline val value: String) : ImageId { /** * 一般是群的图片的 id. - * - * @param md5 用于下载图片时提交 */ class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) : ImageId { override fun toString(): String = "ImageId(value=$value, uniqueId=${uniqueId}, height=$height, width=$width)" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt index 4ec774f53..ea03337f8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt @@ -4,8 +4,8 @@ package net.mamoe.mirai.network.protocol.tim.packet.action import com.soywiz.klock.Date import kotlinx.io.core.* -import net.mamoe.mirai.contact.Gender -import net.mamoe.mirai.contact.Profile +import net.mamoe.mirai.contact.data.Gender +import net.mamoe.mirai.contact.data.Profile import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.utils.io.* diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacketFactory.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacketFactory.kt index 11e88e22f..368ac13fe 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacketFactory.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/EventPacketFactory.kt @@ -64,7 +64,6 @@ object EventPacketFactory : PacketFactory(SessionKey) { } } - operator fun invoke( id: PacketId, sequenceId: UShort, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberKickEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberKickEvent.kt new file mode 100644 index 000000000..adcda2bcd --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MemberKickEvent.kt @@ -0,0 +1,25 @@ +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") + +package net.mamoe.mirai.network.protocol.tim.packet.event + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion + + +/** + * 成员被踢出 + */ +data class MemberKickEvent( + val member: Member +) : EventPacket + +@PacketVersion(date = "2019.11.20", timVersion = "2.3.2 (21173)") +object MemberKickEventPacketFactory : KnownEventParserAndHandler(0x0022u) { + override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): MemberKickEvent { + + TODO() + // return MemberKickEvent() + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt index 573156d48..6568aa94d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/PasswordSubmission.kt @@ -3,7 +3,7 @@ package net.mamoe.mirai.network.protocol.tim.packet.login import kotlinx.io.core.* -import net.mamoe.mirai.contact.Gender +import net.mamoe.mirai.contact.data.Gender import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.packet.* diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index ff26b1ed8..6684ecfcb 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -61,6 +61,7 @@ class ExternalImage( suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) { is Group -> contact.uploadImage(this).sendTo(contact) is QQ -> contact.uploadImage(this).sendTo(contact) + else -> assertUnreachable() } /** @@ -72,6 +73,7 @@ suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) { suspend fun ExternalImage.upload(contact: Contact): Image = when (contact) { is Group -> contact.uploadImage(this).image() is QQ -> contact.uploadImage(this).image() + else -> assertUnreachable() } /**