Make Profile data class

This commit is contained in:
Him188 2019-11-13 20:28:48 +08:00
parent cc2343e220
commit c655c0fe33
12 changed files with 91 additions and 115 deletions

View File

@ -65,7 +65,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope {
val contacts = ContactSystem()
var network: BotNetworkHandler<*> = TIMBotNetworkHandler(this)
var network: BotNetworkHandler<*> = TIMBotNetworkHandler(this.coroutineContext, this)
init {
launch {
@ -90,7 +90,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) : CoroutineScope {
} catch (e: Exception) {
logger.error(e)
}
network = TIMBotNetworkHandler(this)
network = TIMBotNetworkHandler(this.coroutineContext, this)
return network.login(configuration)
}

View File

@ -3,6 +3,7 @@
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
@ -17,6 +18,7 @@ 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
@ -33,7 +35,9 @@ class ContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
sealed class Contact(val bot: Bot, val id: UInt) {
/**
* 向这个对象发送消息. 速度太快会被服务器拒绝(无响应)
* 向这个对象发送消息.
*
* 速度太快会被服务器拒绝(无响应). 在测试中不延迟地发送 6 条消息就会被屏蔽之后的数据包 1 秒左右.
*/
abstract suspend fun sendMessage(message: MessageChain)
@ -65,7 +69,7 @@ fun UInt.groupId(): GroupId = GroupId(this)
*
* : Java 中常用 [Long] 来表示 [UInt]
*/
fun Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0).toUInt())
fun @receiver:PositiveNumbers Long.groupId(): GroupId = GroupId(this.coerceAtLeastOrFail(0).toUInt())
/**
* 一些群 API 使用的 ID. 在使用时会特别注明
@ -95,9 +99,7 @@ class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot,
bot.sendPacket(SendGroupMessagePacket(bot.qqAccount, internalId, bot.sessionKey, message))
}
override fun toString(): String {
return "Group(${this.id})"
}
override fun toString(): String = "Group(${this.id})"
companion object
}
@ -111,6 +113,9 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R = bot.withSessi
/**
* 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.
@ -118,12 +123,17 @@ inline fun <R> Contact.withSession(block: BotSession.() -> R): R = bot.withSessi
* @author Him188moe
*/
open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
// TODO: 2019/11/8 should be suspend val if kotlin supports
val profile: Deferred<Profile> by bot.network.SuspendLazy { updateProfile() }
private var _profile: Profile? = null
private val _initialProfile by bot.network.SuspendLazy { updateProfile() }
override suspend fun sendMessage(message: MessageChain) {
/**
* 用户资料.
*/
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))
}
/**
* 更新个人资料.
@ -137,19 +147,13 @@ open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
* ```
*/
suspend fun updateProfile(): Profile = bot.withSession {
RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey)
.sendAndExpectAsync<RequestProfileDetailsResponse, Profile> { it.profile }
.await().let {
@Suppress("UNCHECKED_CAST")
if ((::profile as SuspendLazy<Profile>).isInitialized()) {
profile.await().apply { copyFrom(it) }
} else it
}
_profile = RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey)
.sendAndExpect<RequestProfileDetailsResponse, Profile> { it.profile }
return _profile!!
}
override fun toString(): String {
return "QQ(${this.id})"
}
override fun toString(): String = "QQ(${this.id})"
}
@ -161,9 +165,7 @@ class Member internal constructor(bot: Bot, id: UInt, val group: Group) : QQ(bot
TODO("Group member implementation")
}
override fun toString(): String {
return "Member(${this.id})"
}
override fun toString(): String = "Member(${this.id})"
}
/**
@ -187,61 +189,20 @@ enum class MemberPermission {
/**
* 个人资料
*/
// FIXME: 2019/11/8 should be `data class Profile`
@Suppress("PropertyName")
class Profile(
internal var _qq: UInt,
internal var _nickname: String,
internal var _zipCode: String?,
internal var _phone: String?,
internal var _gender: Gender,
internal var _birthday: Date?,
internal var _personalStatus: String?,
internal var _school: String?,
internal var _homepage: String?,
internal var _email: String?,
internal var _company: String?
) {
val qq: UInt get() = _qq
val nickname: String get() = _nickname
val zipCode: String? get() = _zipCode
val phone: String? get() = _phone
val gender: Gender get() = _gender
/**
* 个性签名
*/
val personalStatus: String? get() = _personalStatus
val school: String? get() = _school
val company: String? get() = _company
/**
* 主页
*/
val homepage: String? get() = _homepage
val email: String? get() = _email
val birthday: Date? get() = _birthday
override fun toString(): String = "Profile(" +
"qq=$qq, nickname=$nickname, zipCode=$zipCode, phone=$phone, " +
"gender=$gender, birthday=$birthday, personalStatus=$personalStatus, school=$school, " +
"homepage=$homepage, email=$email, company=$company" +
")"
}
fun Profile.copyFrom(another: Profile) {
this._qq = another.qq
this._nickname = another.nickname
this._zipCode = another.zipCode
this._phone = another.phone
this._gender = another.gender
this._birthday = another.birthday
this._personalStatus = another.personalStatus
this._school = another.school
this._homepage = another.homepage
this._email = another.email
this._company = another.company
}
data class Profile(
val qq: UInt,
val nickname: String,
val zipCode: String?,
val phone: String?,
val gender: Gender,
val birthday: Date?,
val personalStatus: String?,
val school: String?,
val homepage: String?,
val email: String?,
val company: String?
)
/**
* 性别

View File

@ -8,10 +8,16 @@ enum class MessageType(val value: UByte) {
PLAIN_TEXT(0x01u),
AT(0x06u),
FACE(0x02u),
GROUP_IMAGE(0x03u),
FRIEND_IMAGE(0x06u),
/**
* [ImageId.value] 长度为 42 的图片
*/
IMAGE_42(0x03u),
/**
* [ImageId.value] 长度为 37 的图片
*/
IMAGE_37(0x06u),
;
val intValue: Int = this.value.toInt()
inline val intValue: Int get() = this.value.toInt()
}

View File

@ -145,7 +145,7 @@ fun ByteReadPacket.readMessageChain(): MessageChain {
return chain
}
fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
this@toPacket.forEach { message ->
writePacket(with(message) {
when (this) {
@ -170,7 +170,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
when (id.value.length) {
// "{F61593B5-5B98-1798-3F47-2A91D32ED2FC}.jpg"
42 -> {
writeUByte(MessageType.GROUP_IMAGE.value)
writeUByte(MessageType.IMAGE_42.value)
//00 00 03 00 CB 02 00 2A 7B 46 36 31 35 39 33 42 35 2D 35 42 39 38 2D 31 37 39 38 2D 33 46 34 37 2D 32 41 39 31 44 33 32 45 44 32 46 43 7D 2E 6A 70 67
// 04 00 04 87 E5 09 3B 05 00 04 D2 C4 C0 B7 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 01 ED 16 00 04 00 00 02 17 18 00 04 00 00 EB 34 FF 00 5C 15 36 20 39 32 6B 41 31 43 38 37 65 35 30 39 33 62 64 32 63 34 63 30 62 37 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20
@ -191,7 +191,7 @@ fun MessageChain.toPacket(forGroup: Boolean): ByteReadPacket = buildPacket {
// "/01ee6426-5ff1-4cf0-8278-e8634d2909ef"
37 -> {
writeUByte(MessageType.FRIEND_IMAGE.value)
writeUByte(MessageType.IMAGE_37.value)
// 00 00 06 00 F3 02
// 00 1B 24 5B 56 54 4A 38 60 4C 5A 4E 46 7D 53 39 4F 52 36 25 45 60 42 55 53 2E 6A 70 67

View File

@ -25,7 +25,6 @@ import net.mamoe.mirai.utils.getGTK
import net.mamoe.mirai.utils.internal.PositiveNumbers
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
import kotlin.coroutines.coroutineContext
import kotlin.jvm.JvmField
/**
* 构造 [BotSession] 的捷径
@ -60,7 +59,6 @@ class BotSession(
*/
val sKey: String get() = _sKey
@JvmField
@Suppress("PropertyName")
internal var _sKey: String = ""
set(value) {
@ -73,7 +71,6 @@ class BotSession(
*/
val gtk: Int get() = _gtk
@JvmField
private var _gtk: Int = 0
/**
@ -139,6 +136,9 @@ class BotSession(
* ```
* @sample Bot.addFriend 添加好友
*/
suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true, crossinline block: (P) -> R): R =
sendAndExpectAsync<P, R>(checkSequence) { block(it) }.await()
suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true): P =
sendAndExpectAsync<P, P>(checkSequence) { it }.await()

View File

@ -38,11 +38,11 @@ internal expect val NetworkDispatcher: CoroutineDispatcher
*
* @see BotNetworkHandler
*/
internal class TIMBotNetworkHandler internal constructor(override inline val bot: Bot) :
internal class TIMBotNetworkHandler internal constructor(coroutineContext: CoroutineContext, override inline val bot: Bot) :
BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, PacketHandlerList() {
override val coroutineContext: CoroutineContext =
NetworkDispatcher + CoroutineExceptionHandler { _, e ->
coroutineContext + NetworkDispatcher + CoroutineExceptionHandler { _, e ->
bot.logger.error("An exception was thrown in a coroutine under TIMBotNetworkHandler", e)
} + SupervisorJob()

View File

@ -29,7 +29,7 @@ class TemporaryPacketHandler<P : Packet, R>(
private val fromSession: BotSession,
private val checkSequence: Boolean,
/**
* 调用者的 [CoroutineContext]
* 调用者的 [CoroutineContext]. 包处理过程将会在这个 context 下运行
*/
private val callerContext: CoroutineContext
) {

View File

@ -61,23 +61,23 @@ object RequestProfileDetailsPacket : SessionPacketFactory<RequestProfileDetailsR
discardExact(6)
val map = readTLVMap(tagSize = 2, expectingEOF = true)
val profile = Profile(
_qq = qq,
_nickname = map[0x4E22u]?.stringOfWitch() ?: "",//error("Cannot determine nickname")
_zipCode = map[0x4E25u]?.stringOfWitch(),
_phone = map[0x4E27u]?.stringOfWitch(),
_gender = when (map[0x4E29u]?.let { it[0] }?.toUInt()) {
qq = qq,
nickname = map[0x4E22u]?.stringOfWitch() ?: "",//error("Cannot determine nickname")
zipCode = map[0x4E25u]?.stringOfWitch(),
phone = map[0x4E27u]?.stringOfWitch(),
gender = when (map[0x4E29u]?.let { it[0] }?.toUInt()) {
null -> Gender.SECRET //error("Cannot determine gender, entry 0x4E29u not found")
0x02u -> Gender.FEMALE
0x01u -> Gender.MALE
else -> Gender.SECRET // 猜的
//else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toUHexString()}")
},
_birthday = map[0x4E3Fu]?.let { Date(it.toUInt().toInt()) },
_personalStatus = map[0x4E33u]?.stringOfWitch(),
_homepage = map[0x4E2Du]?.stringOfWitch(),
_company = map[0x5DC8u]?.stringOfWitch(),
_school = map[0x4E35u]?.stringOfWitch(),
_email = map[0x4E2Bu]?.stringOfWitch()
birthday = map[0x4E3Fu]?.let { Date(it.toUInt().toInt()) },
personalStatus = map[0x4E33u]?.stringOfWitch(),
homepage = map[0x4E2Du]?.stringOfWitch(),
company = map[0x5DC8u]?.stringOfWitch(),
school = map[0x4E35u]?.stringOfWitch(),
email = map[0x4E2Bu]?.stringOfWitch()
)
map.clear()

View File

@ -52,7 +52,7 @@ object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Re
writeHex(TIMProtocol.messageConstNewest)
writeZero(2)
writePacket(message.toPacket(false))
writePacket(message.toPacket())
/*
//Plain text

View File

@ -35,7 +35,7 @@ object SendGroupMessagePacket : SessionPacketFactory<SendGroupMessagePacket.Resp
writeHex(TIMProtocol.messageConst1)
writeZero(2)
writePacket(message.toPacket(true))
writePacket(message.toPacket())
}
/*it.writeByte(0x01)
it.writeShort(bytes.size + 3)

View File

@ -27,7 +27,7 @@ fun <R> CoroutineScope.SuspendLazy(initializer: suspend () -> R): Lazy<Deferred<
* @sample QQ.profile
*/
@PublishedApi
internal class SuspendLazy<R>(scope: CoroutineScope, val initializer: suspend () -> R) : Lazy<Deferred<R>> {
internal class SuspendLazy<R>(scope: CoroutineScope, initializer: suspend () -> R) : Lazy<Deferred<R>> {
private val valueUpdater: Deferred<R> by lazy { scope.async { initializer() } }
@Suppress("EXPERIMENTAL_API_USAGE")

View File

@ -1,30 +1,39 @@
package net.mamoe.mirai.utils.internal
/**
* 要求 [this] 最小为 [min].
*/
@PublishedApi
internal fun Int.coerceAtLeastOrFail(value: Int): Int {
require(this > value)
internal fun Int.coerceAtLeastOrFail(min: Int): Int {
require(this >= min)
return this
}
/**
* 要求 [this] 最小为 [min].
*/
@PublishedApi
internal fun Long.coerceAtLeastOrFail(value: Long): Long {
require(this > value)
internal fun Long.coerceAtLeastOrFail(min: Long): Long {
require(this >= min)
return this
}
/**
* 要求 [this] 最大为 [max].
*/
@PublishedApi
internal fun Int.coerceAtMostOrFail(maximumValue: Int): Int =
if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue")
internal fun Int.coerceAtMostOrFail(max: Int): Int =
if (this >= max) error("value is greater than its expected maximum value $max")
else this
@PublishedApi
internal fun Long.coerceAtMostOrFail(maximumValue: Long): Long =
if (this > maximumValue) error("value is greater than its expected maximum value $maximumValue")
internal fun Long.coerceAtMostOrFail(max: Long): Long =
if (this >= max) error("value is greater than its expected maximum value $max")
else this
/**
* 表示这个参数必须为正数
* 表示这个参数必须为正数. 仅用于警示
*/
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.VALUE_PARAMETER)
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.TYPE)
internal annotation class PositiveNumbers