mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-24 23:20:09 +08:00
Redesign Contacts: Use interfaces and hide internal implementations
This commit is contained in:
parent
1a56235cb0
commit
b797ef3cc1
@ -7,6 +7,8 @@ import kotlinx.coroutines.sync.Mutex
|
|||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import net.mamoe.mirai.Bot.ContactSystem
|
import net.mamoe.mirai.Bot.ContactSystem
|
||||||
import net.mamoe.mirai.contact.*
|
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.BotNetworkHandler
|
||||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
|
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
|
||||||
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
|
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
|
* @see Bot.contacts
|
||||||
*/
|
*/
|
||||||
inner class ContactSystem internal constructor() {
|
inner class ContactSystem internal constructor() {
|
||||||
inline val bot: Bot get() = this@Bot
|
val bot: Bot get() = this@Bot
|
||||||
|
|
||||||
private val _groups = ContactList<Group>()
|
@Suppress("PropertyName")
|
||||||
private lateinit var groupsUpdater: Job
|
internal val _groups = MutableContactList<Group>()
|
||||||
val groups = ContactList<Group>()
|
internal lateinit var groupsUpdater: Job
|
||||||
private val groupsLock = Mutex()
|
private val groupsLock = Mutex()
|
||||||
|
|
||||||
private val _qqs = ContactList<QQ>() //todo 实现群列表和好友列表获取
|
val groups: ContactList<Group> = ContactList(_groups)
|
||||||
private lateinit var qqUpdaterJob: Job
|
|
||||||
val qqs: ContactList<QQ> = _qqs
|
@Suppress("PropertyName")
|
||||||
|
internal val _qqs = MutableContactList<QQ>() //todo 实现群列表和好友列表获取
|
||||||
|
internal lateinit var qqUpdaterJob: Job
|
||||||
private val qqsLock = Mutex()
|
private val qqsLock = Mutex()
|
||||||
|
|
||||||
|
val qqs: ContactList<QQ> = ContactList(_qqs)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
|
* 获取缓存的 QQ 对象. 若没有对应的缓存, 则会创建一个.
|
||||||
*
|
*
|
||||||
* 注: 这个方法是线程安全的
|
* 注: 这个方法是线程安全的
|
||||||
*/
|
*/
|
||||||
suspend fun getQQ(id: UInt): QQ =
|
suspend fun getQQ(id: UInt): QQ =
|
||||||
if (qqs.containsKey(id)) qqs[id]!!
|
if (_qqs.containsKey(id)) _qqs[id]!!
|
||||||
else qqsLock.withLock {
|
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 {
|
suspend fun getGroup(id: GroupId): Group = id.value.let {
|
||||||
if (groups.containsKey(it)) groups[it]!!
|
if (_groups.containsKey(it)) _groups[it]!!
|
||||||
else groupsLock.withLock {
|
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())
|
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() {
|
suspend fun close() {
|
||||||
network.close()
|
network.close()
|
||||||
this.coroutineContext.cancelChildren()
|
this.coroutineContext.cancelChildren()
|
||||||
contacts.groups.clear()
|
contacts._groups.clear()
|
||||||
contacts.qqs.clear()
|
contacts._qqs.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -38,14 +38,12 @@ suspend inline fun Bot.getGroup(internalId: GroupInternalId): Group = this.conta
|
|||||||
/**
|
/**
|
||||||
* 取得机器人的群成员列表
|
* 取得机器人的群成员列表
|
||||||
*/
|
*/
|
||||||
inline val Bot.groups: ContactList<Group>
|
inline val Bot.groups: ContactList<Group> get() = this.contacts.groups
|
||||||
get() = this.contacts.groups
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 取得机器人的好友列表
|
* 取得机器人的好友列表
|
||||||
*/
|
*/
|
||||||
inline val Bot.qqs: ContactList<QQ>
|
inline val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
|
||||||
get() = this.contacts.qqs
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
|
* 以 [BotSession] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
|
||||||
|
@ -1,48 +1,40 @@
|
|||||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate")
|
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||||
|
|
||||||
package net.mamoe.mirai.contact
|
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.Bot
|
||||||
import net.mamoe.mirai.message.Message
|
import net.mamoe.mirai.message.Message
|
||||||
import net.mamoe.mirai.message.MessageChain
|
import net.mamoe.mirai.message.MessageChain
|
||||||
import net.mamoe.mirai.message.singleChain
|
import net.mamoe.mirai.message.singleChain
|
||||||
import net.mamoe.mirai.network.BotSession
|
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 net.mamoe.mirai.withSession
|
||||||
import kotlin.contracts.ExperimentalContracts
|
import kotlin.contracts.ExperimentalContracts
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
class ContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group].
|
* 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group].
|
||||||
*
|
*
|
||||||
* @param bot 这个联系人所属 [Bot]
|
|
||||||
* @param id 可以是 QQ 号码或者群号码 [GroupId].
|
|
||||||
*
|
|
||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
sealed class Contact(val bot: Bot, val id: UInt) {
|
interface Contact {
|
||||||
|
/**
|
||||||
|
* 这个联系人所属 [Bot]
|
||||||
|
*/
|
||||||
|
val bot: Bot
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 可以是 QQ 号码或者群号码 [GroupId].
|
||||||
|
*/
|
||||||
|
val id: UInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 向这个对象发送消息.
|
* 向这个对象发送消息.
|
||||||
*
|
*
|
||||||
* 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
|
* 速度太快会被服务器屏蔽(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
|
||||||
*/
|
*/
|
||||||
abstract suspend fun sendMessage(message: MessageChain)
|
suspend fun sendMessage(message: MessageChain)
|
||||||
|
|
||||||
|
|
||||||
//这两个方法应写为扩展函数, 但为方便 import 还是写在这里
|
//这两个方法应写为扩展函数, 但为方便 import 还是写在这里
|
||||||
@ -51,62 +43,6 @@ sealed class Contact(val bot: Bot, val id: UInt) {
|
|||||||
suspend fun sendMessage(message: Message) = sendMessage(message.singleChain())
|
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<Member>
|
|
||||||
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] 作为接收器 (receiver) 并调用 [block], 返回 [block] 的返回值.
|
||||||
* 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync]
|
* 这个方法将能帮助使用在 [BotSession] 中定义的一些扩展方法, 如 [BotSession.sendAndExpectAsync]
|
||||||
@ -120,117 +56,11 @@ inline fun <R> 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) {
|
class ContactList<C : Contact> internal constructor(private val delegate: MutableContactList<C>) : Map<UInt, C> by delegate
|
||||||
private var _profile: Profile? = null
|
|
||||||
private val _initialProfile by bot.network.SuspendLazy { updateProfile() }
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户资料.
|
* 可修改联系人列表. 只会在内部使用.
|
||||||
*/
|
*/
|
||||||
val profile: Deferred<Profile>
|
internal class MutableContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
|
||||||
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<RequestProfileDetailsResponse, Profile> { it.profile }
|
|
||||||
|
|
||||||
return _profile!!
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "QQ(${this.id})"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 群成员
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
@ -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<Member>
|
||||||
|
|
||||||
|
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)
|
@ -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;
|
||||||
|
}
|
@ -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<Profile>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新个人资料.
|
||||||
|
* 将会同步更新 property [profile]
|
||||||
|
*/
|
||||||
|
suspend fun updateProfile(): Profile
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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<Member> = MutableContactList()
|
||||||
|
override val member: ContactList<Member> = 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<Profile> 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<RequestProfileDetailsResponse, Profile> { 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<Profile> 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})"
|
||||||
|
}
|
@ -170,8 +170,6 @@ inline class ImageId0x06(override inline val value: String) : ImageId {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 一般是群的图片的 id.
|
* 一般是群的图片的 id.
|
||||||
*
|
|
||||||
* @param md5 用于下载图片时提交
|
|
||||||
*/
|
*/
|
||||||
class ImageId0x03 constructor(override inline val value: String, inline val uniqueId: UInt, inline val height: Int, inline val width: Int) : ImageId {
|
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)"
|
override fun toString(): String = "ImageId(value=$value, uniqueId=${uniqueId}, height=$height, width=$width)"
|
||||||
|
@ -4,8 +4,8 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
|
|||||||
|
|
||||||
import com.soywiz.klock.Date
|
import com.soywiz.klock.Date
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
import net.mamoe.mirai.contact.Gender
|
import net.mamoe.mirai.contact.data.Gender
|
||||||
import net.mamoe.mirai.contact.Profile
|
import net.mamoe.mirai.contact.data.Profile
|
||||||
import net.mamoe.mirai.network.BotNetworkHandler
|
import net.mamoe.mirai.network.BotNetworkHandler
|
||||||
import net.mamoe.mirai.network.protocol.tim.packet.*
|
import net.mamoe.mirai.network.protocol.tim.packet.*
|
||||||
import net.mamoe.mirai.utils.io.*
|
import net.mamoe.mirai.utils.io.*
|
||||||
|
@ -64,7 +64,6 @@ object EventPacketFactory : PacketFactory<Packet, SessionKey>(SessionKey) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
operator fun invoke(
|
operator fun invoke(
|
||||||
id: PacketId,
|
id: PacketId,
|
||||||
sequenceId: UShort,
|
sequenceId: UShort,
|
||||||
|
@ -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<MemberKickEvent>(0x0022u) {
|
||||||
|
override suspend fun ByteReadPacket.parse(bot: Bot, identity: EventPacketIdentity): MemberKickEvent {
|
||||||
|
|
||||||
|
TODO()
|
||||||
|
// return MemberKickEvent()
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@
|
|||||||
package net.mamoe.mirai.network.protocol.tim.packet.login
|
package net.mamoe.mirai.network.protocol.tim.packet.login
|
||||||
|
|
||||||
import kotlinx.io.core.*
|
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.BotNetworkHandler
|
||||||
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
|
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
|
||||||
import net.mamoe.mirai.network.protocol.tim.packet.*
|
import net.mamoe.mirai.network.protocol.tim.packet.*
|
||||||
|
@ -61,6 +61,7 @@ class ExternalImage(
|
|||||||
suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) {
|
suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) {
|
||||||
is Group -> contact.uploadImage(this).sendTo(contact)
|
is Group -> contact.uploadImage(this).sendTo(contact)
|
||||||
is QQ -> 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) {
|
suspend fun ExternalImage.upload(contact: Contact): Image = when (contact) {
|
||||||
is Group -> contact.uploadImage(this).image()
|
is Group -> contact.uploadImage(this).image()
|
||||||
is QQ -> contact.uploadImage(this).image()
|
is QQ -> contact.uploadImage(this).image()
|
||||||
|
else -> assertUnreachable()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user