From 61b10db854c1757d789bb23a61baf72f99fdc74c Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 2 Nov 2019 18:31:01 +0800 Subject: [PATCH] Profile suppport --- README.md | 1 + .../commonMain/kotlin/net.mamoe.mirai/Bot.kt | 14 +- .../kotlin/net.mamoe.mirai/contact/Contact.kt | 74 ++++++++- .../event/events/FriendEvents.kt | 8 +- .../kotlin/net.mamoe.mirai/message/Message.kt | 4 +- .../protocol/tim/packet/OutgoingPacket.kt | 26 ++++ .../network/protocol/tim/packet/Packet.kt | 3 +- .../protocol/tim/packet/action/Profile.kt | 146 ++++++++++++++++++ .../tim/packet/action/ProfilePicture.kt | 16 -- .../tim/packet/event/MessageEventPackets.kt | 16 +- .../tim/packet/login/ServerLoginResponse.kt | 7 +- .../main/kotlin/demo/gentleman/GentleImage.kt | 39 ++--- .../src/main/kotlin/demo/gentleman/Main.kt | 23 +-- .../src/main/kotlin/demo/gentleman/Utils.kt | 42 ----- 14 files changed, 310 insertions(+), 109 deletions(-) create mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt delete mode 100644 mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/ProfilePicture.kt delete mode 100644 mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Utils.kt diff --git a/README.md b/README.md index f560b3d9e..74b05a80c 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Mirai 在 JVM 平台采用插件模式运行,同时提供独立的跨平台核 - 上传并发送好友/群图片(10/21, 10/26) - 群员权限改变(11/2) - 发起会话(11/2) +- 个人资料(11/2) 计划中: 添加好友 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 57a4e34a7..84005160f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -105,10 +105,10 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { * * 注: 这个方法是线程安全的 */ - suspend fun getQQ(account: UInt): QQ = - if (qqs.containsKey(account)) qqs[account]!! + suspend fun getQQ(id: UInt): QQ = + if (qqs.containsKey(id)) qqs[id]!! else qqsLock.withLock { - qqs.getOrPut(account) { QQ(this@Bot, account) } + qqs.getOrPut(id) { QQ(this@Bot, id) } } /** @@ -129,6 +129,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { groups.getOrPut(it) { Group(this@Bot, id) } } } + } suspend inline fun Int.qq(): QQ = getQQ(this.coerceAtLeastOrFail(0).toUInt()) @@ -150,4 +151,9 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { companion object { val instances: MutableList = mutableListOf() } -} \ No newline at end of file +} + +/** + * 添加一个好友 + */ +suspend fun ContactSystem.addFriend(id: UInt): Nothing = TODO() \ No newline at end of file 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 591294cee..361d3d81f 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,13 +1,18 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.contact +import com.soywiz.klock.Date +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.handler.EventPacketHandler +import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsPacket +import net.mamoe.mirai.qqAccount +import net.mamoe.mirai.utils.SuspendLazy import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail import net.mamoe.mirai.withSession @@ -23,6 +28,9 @@ class ContactList : MutableMap by mutableMapOf() */ sealed class Contact(val bot: Bot, val id: UInt) { + /** + * 向这个对象发送消息. 速度太快会被服务器拒绝(无响应) + */ abstract suspend fun sendMessage(message: MessageChain) @@ -102,11 +110,40 @@ inline fun Contact.withSession(block: BotSession.() -> R): R = bot.withSessi * @author Him188moe */ open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) { + val profile: Deferred by bot.network.SuspendLazy { updateProfile() } + override suspend fun sendMessage(message: MessageChain) { bot.network[EventPacketHandler].sendFriendMessage(this, message) } + + /** + * 更新个人资料. + * + * 这个方法会尽可能更新已有的 [Profile] 对象的值, 而不是用新的对象替换 + * 若 [QQ.profile] 已经初始化, 则在获取到新的 profile 时通过 [Profile.copyFrom] 来更新已有的 [QQ.profile]. 仍然返回 [QQ.profile] + * 因此, 对于以下代码: + * ```kotlin + * val old = qq.profile + * qq.updateProfile() === old // true, 因为只是更新了 qq.profile 的值 + * ``` + */ + suspend fun updateProfile(): Profile = bot.withSession { + RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey) + .sendAndExpect { it.profile } + .await().let { + @Suppress("UNCHECKED_CAST") + if ((::profile as SuspendLazy).isInitialized()) { + profile.await().apply { copyFrom(it) } + } else it + } + } + + suspend fun QQ.addAsFriend() { + + } } + /** * 群成员 */ @@ -114,4 +151,39 @@ class Member internal constructor(bot: Bot, id: UInt, val group: Group) : QQ(bot init { TODO("Group member implementation") } +} + +/** + * 个人资料 + */ +class Profile// inline class Date + (qq: UInt, nickname: String, zipCode: String?, phone: String?, gender: Gender, var birthday: Date?) { + + var qq: UInt = qq + internal set + var nickname: String = nickname + internal set + var zipCode: String? = zipCode + internal set + var phone: String? = phone + internal set + var gender: Gender = gender + internal set +} + +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 +} + +/** + * 性别 + */ +enum class Gender { + SECRET, + MALE, + FEMALE; } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendEvents.kt index 2e174783b..e43f3ffa4 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendEvents.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/FriendEvents.kt @@ -3,6 +3,7 @@ package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Profile import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.MessageChain @@ -34,4 +35,9 @@ class FriendConversationInitializedEvent(bot: Bot, sender: QQ) : FriendEvent(bot /** * 好友在线状态改变事件 */ -class FriendOnlineStatusChangedEvent(bot: Bot, sender: QQ, val newStatus: OnlineStatus) : FriendEvent(bot, sender) \ No newline at end of file +class FriendOnlineStatusChangedEvent(bot: Bot, sender: QQ, val newStatus: OnlineStatus) : FriendEvent(bot, sender) + +/** + * 好友个人资料更新 + */ +class FriendProfileUpdatedEvent(bot: Bot, qq: QQ, val profile: Profile) : FriendEvent(bot, qq) \ 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 db286c89d..c4dd09abd 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 @@ -146,7 +146,7 @@ fun String.singleChain(): MessageChain = this.toMessage().singleChain() * * @param id 这个图片的 [ImageId] */ -inline class Image(val id: ImageId) : Message { +inline class Image(inline val id: ImageId) : Message { override val stringValue: String get() = "[${id.value}]" override fun toString(): String = stringValue @@ -161,7 +161,7 @@ inline class Image(val id: ImageId) : Message { * @see ExternalImage.groupImageId 群图片的 [ImageId] 获取 * @see FriendImageIdRequestPacket.Response.imageId 好友图片的 [ImageId] 获取 */ -inline class ImageId(val value: String) +inline class ImageId(inline val value: String) fun ImageId.image(): Image = Image(this) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt index 928225cb7..c2815213a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/OutgoingPacket.kt @@ -8,7 +8,9 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.use import kotlinx.io.core.writeUShort import net.mamoe.mirai.network.protocol.tim.TIMProtocol +import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.writeHex +import net.mamoe.mirai.utils.io.writeQQ import kotlin.jvm.JvmOverloads /** @@ -55,6 +57,7 @@ interface OutgoingPacketBuilder { /** * 构造一个待发送给服务器的数据包. + * * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id. */ @JvmOverloads @@ -76,4 +79,27 @@ fun OutgoingPacketBuilder.buildOutgoingPacket( } return OutgoingPacket(name, id, sequenceId, it.build()) } +} + + +/** + * 构造一个待发送给服务器的会话数据包. + * + * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id. + */ +@JvmOverloads +fun OutgoingPacketBuilder.buildSessionPacket( + bot: UInt, + sessionKey: ByteArray, + name: String? = null, + id: PacketId = this.annotatedId.id, + sequenceId: UShort = OutgoingPacketBuilder.atomicNextSequenceId(), + headerSizeHint: Int = 0, + block: BytePacketBuilder.() -> Unit +): OutgoingPacket = buildOutgoingPacket(name, id, sequenceId, headerSizeHint) { + writeQQ(bot) + writeHex(TIMProtocol.version0x02) + encryptAndWrite(sessionKey) { + block() + } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt index ca3d83d05..51828fc37 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/Packet.kt @@ -86,7 +86,8 @@ enum class KnownPacketId(override inline val value: UShort, internal inline val inline GROUP_IMAGE_ID(0x0388u, GroupImageIdRequestPacket), inline FRIEND_IMAGE_ID(0x0352u, FriendImageIdRequestPacket), - inline REQUEST_PROFILE(0x00_31u, RequestProfilePicturePacket), + inline REQUEST_PROFILE_AVATAR(0x00_31u, RequestProfilePicturePacket), + inline REQUEST_PROFILE_DETAILS(0x00_3Cu, RequestProfilePicturePacket), @Suppress("DEPRECATION") inline SUBMIT_IMAGE_FILE_NAME(0x01_BDu, SubmitImageFilenamePacket), 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 new file mode 100644 index 000000000..b699b7f08 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/Profile.kt @@ -0,0 +1,146 @@ +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") + +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.network.protocol.tim.packet.* +import net.mamoe.mirai.utils.io.* +import kotlin.properties.Delegates + +// 用户资料的头像 +/** + * 请求获取头像 + */ +@AnnotatedId(KnownPacketId.REQUEST_PROFILE_AVATAR) +object RequestProfilePicturePacket : OutgoingPacketBuilder { + operator fun invoke(): OutgoingPacket = buildOutgoingPacket { + TODO() + } +} + +/** + * 请求账号详细信息. + * + * @see Profile + */ +@AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS) +object RequestProfileDetailsPacket : OutgoingPacketBuilder { + + //00 01 3E F8 FB E3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 + //00 01 B1 89 BE 09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 + //00 01 87 73 86 9D 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5 + operator fun invoke( + bot: UInt, + qq: UInt, + sessionKey: ByteArray + ): OutgoingPacket = buildSessionPacket(bot, sessionKey) { + writeUShort(0x01u) + writeUInt(qq) + writeHex("00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 29 4E 22 4E 25 4E 26 4E 27 4E 29 4E 2A 4E 2B 4E 2D 4E 2E 4E 2F 4E 30 4E 31 4E 33 4E 35 4E 36 4E 37 4E 38 4E 3F 4E 40 4E 41 4E 42 4E 43 4E 45 4E 49 4E 4B 4E 4F 4E 54 4E 5B 52 0B 52 0F 5D C2 5D C8 65 97 69 9D 69 A9 9D A5 A4 91 A4 93 A4 94 A4 9C A4 B5") + } + + @AnnotatedId(KnownPacketId.REQUEST_PROFILE_DETAILS) + class Response(input: ByteReadPacket) : ResponsePacket(input) { + var qq: UInt by Delegates.notNull() + lateinit var profile: Profile + + //00 01 00 99 6B F8 D2 00 00 00 00 00 29 + // 4E 22 00 0F E4 B8 8B E9 9B A8 E6 97 B6 E6 B5 81 E6 B3 AA 4E 25 00 00 4E 26 00 0C E4 B8 AD E5 9B BD E6 B2 B3 E5 8C 97 4E 27 00 0B 30 33 31 39 39 39 39 39 39 39 39 + // 4E 29 [00 01] 01 4E 2A 00 00 4E 2B 00 17 6D 61 69 6C 2E 71 71 32 35 37 33 39 39 30 30 39 38 2E 40 2E 63 6F 6D 4E 2D 00 00 4E 2E 00 02 31 00 4E 2F 00 04 36 37 38 00 4E 30 00 00 4E 31 00 01 00 4E 33 00 00 4E 35 00 00 4E 36 00 01 00 4E 37 00 01 00 4E 38 00 01 00 4E 3F 00 04 07 C1 01 01 4E 40 00 0C 00 00 00 00 00 00 00 00 00 00 00 00 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 22 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 00 52 0B 00 04 00 C0 00 01 52 0F 00 14 00 00 00 00 00 00 00 00 12 00 00 48 09 10 00 00 00 00 00 00 5D C2 00 0C 00 00 00 00 00 00 00 00 00 00 00 00 5D C8 00 00 65 97 00 01 08 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 01 A4 91 00 02 00 00 A4 93 00 02 00 00 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00 + + //00 01 00 87 73 86 9D 00 00 00 00 00 29 4E 22 00 15 E6 98 AF E6 9C 9D E8 8F 8C E4 B8 8D E7 9F A5 E6 99 A6 E6 9C 94 4E 25 00 00 4E 26 00 00 4E 27 00 00 + // 4E 29 [00 01] 01 4E 2A 00 00 4E 2B 00 00 4E 2D 00 00 4E 2E 00 02 31 00 4E 2F 00 04 37 32 30 00 4E 30 00 00 4E 31 00 01 01 4E 33 00 00 4E 35 00 00 4E 36 00 01 00 4E 37 00 01 04 4E 38 00 01 00 4E 3F 00 04 07 CF 00 00 4E 40 00 0C 00 00 00 00 00 00 00 00 00 00 00 00 4E 41 00 02 00 00 4E 42 00 02 00 00 4E 43 00 02 00 00 4E 45 00 01 13 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 00 4E 54 00 00 4E 5B 00 04 00 00 00 00 52 0B 00 04 13 80 02 00 52 0F 00 14 00 04 02 00 00 00 00 00 12 04 10 58 89 50 C0 00 22 00 00 00 5D C2 00 0C 00 00 00 00 00 00 00 00 00 00 00 00 5D C8 00 00 65 97 00 01 08 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 01 A4 91 00 02 00 00 A4 93 00 02 00 01 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00 + + //00 01 00 76 E4 B8 DD + // 00 00 00 00 00 29 + + // 4E 22 [00 0E] 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E //昵称 + // 4E 25 [00 06] 34 33 33 31 30 30 //邮编 + // 4E 26 [00 09] E4 B8 8D E7 9F A5 E9 81 93 //? + // 4E 27 [00 0A] 31 33 38 2A 2A 2A 2A 2A 2A 2A // 手机号 + // 4E 29 [00 01] 02 性别, 女02, 男01 + // 4E 2A [00 00] + // 4E 2B [00 00] + // 4E 2D [00 23] 68 74 74 70 3A 2F 2F 77 77 77 2E 34 33 39 39 2E 63 6F 6D 2F 66 6C 61 73 68 2F 33 32 39 37 39 2E 68 74 6D //http://www.4399.com/flash/32979.htm //??? + // 4E 2E [00 02] 31 00 + // 4E 2F [00 04] 36 30 33 00 + // 4E 30 [00 00] + // 4E 31 [00 01] 00 + // 4E 33 [00 00] + // 4E 35 [00 00] + // 4E 36 [00 01] 0A + // 4E 37 [00 01] 06 + // 4E 38 [00 01] 00 + // 4E 3F [00 04] 07 DD 0B 13 生日 short byte byte + // 4E 40 [00 0C] 00 41 42 57 0// 0 00 00 00 00 00 00 00 + // 4E 41 [00 02] 08 04 + // 4E 42 [00 02] 00 00 + // 4E 43 [00 02] 0C 04 + // 4E 45 [00 01] 05 + // 4E 49 [00 04] 00 00 00 00 + // 4E 4B [00 04] 00 00 00 00 + // 4E 4F [00 01] 06 + // 4E 54 [00 00] + // 4E 5B [00 04] 00 00 00 00 + // 52 0B [00 04] 13 80 02 00 + // 52 0F [00 14] 01 00 00 00 00 00 00 00 52 00 40 48 89 50 80 02 00 00 03 00 + // 5D C2 [00 0C] 00 41 42 57 00 00 00 00 00 00 00 00 + // 5D C8 [00 00] + // 65 97 [00 01] 07 + // 69 9D [00 04] 00 00 00 00 + // 69 A9 [00 00] + // 9D A5 [00 02] 00 01 + // A4 91 [00 02] 00 00 + // A4 93 [00 02] 00 01 + // A4 94 [00 02] 00 00 + // A4 9C [00 02] 00 00 + // A4 B5 [00 02] 00 00 + + /* + 00 01 00 76 E4 B8 DD 00 00 00 00 00 29 + 4E 22 00 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 4E 25 00 06 34 33 33 31 30 30 4E 26 00 09 E4 B8 8D E7 9F A5 E9 81 93 4E 27 00 0A 31 33 38 2A 2A 2A 2A 2A 2A 2A 4E 29 00 01 01 4E 2A 00 00 4E 2B 00 00 4E 2D 00 23 68 74 74 70 3A 2F 2F 77 77 77 2E 34 33 39 39 2E 63 6F 6D 2F 66 6C 61 73 68 2F 33 32 39 37 39 2E 68 74 6D 4E 2E 00 02 31 00 4E 2F 00 04 36 30 33 00 4E 30 00 00 4E 31 00 01 00 4E 33 00 00 4E 35 00 00 4E 36 00 01 0A 4E 37 00 01 06 4E 38 00 01 00 4E 3F 00 04 07 DD 0B 13 4E 40 00 0C 00 41 42 57 00 00 00 00 00 00 00 00 4E 41 00 02 08 04 4E 42 00 02 00 00 4E 43 00 02 0C 04 4E 45 00 01 05 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 06 4E 54 00 00 4E 5B 00 04 00 00 00 00 52 0B 00 04 13 80 02 00 52 0F 00 14 01 00 00 00 00 00 00 00 52 00 40 48 89 50 80 02 00 00 03 00 5D C2 00 0C 00 41 42 57 00 00 00 00 00 00 00 00 5D C8 00 00 65 97 00 01 07 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 01 A4 91 00 02 00 00 A4 93 00 02 00 01 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00 + */ + + override fun decode() = with(input) { + discardExact(3) + qq = readUInt() + discardExact(6) + val map = readTLVMap(tagSize = 2, expectingEOF = true) + profile = Profile( + qq = qq, + nickname = (map[0x4E22u] ?: error("Cannot determine nickname")).stringOfWitch(), + zipCode = map[0x4E25u]?.stringOfWitch(), + phone = map[0x4E27u]?.stringOfWitch(), + gender = when (map[0x4E29u]?.let { it[0] }?.toUInt()) { + null -> error("Cannot determine gender, entry 0x4E29u not found") + 0x02u -> Gender.FEMALE + 0x01u -> Gender.MALE + 0x00u -> Gender.SECRET // 猜的 + else -> error("Cannot determine gender, bad value of 0x4E29u: ${map[0x4729u]!![0].toUHexString()}") + }, + birthday = map[0x4E3Fu]?.let { Date(it.toUInt().toInt()) } + ) + map.clear() + } + } +} + +fun main() { + val mapFemale = + "4E 22 00 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 4E 25 00 06 34 33 33 31 30 30 4E 26 00 09 E4 B8 8D E7 9F A5 E9 81 93 4E 27 00 0A 31 33 38 2A 2A 2A 2A 2A 2A 2A 4E 29 00 01 02 4E 2A 00 00 4E 2B 00 00 4E 2D 00 23 68 74 74 70 3A 2F 2F 77 77 77 2E 34 33 39 39 2E 63 6F 6D 2F 66 6C 61 73 68 2F 33 32 39 37 39 2E 68 74 6D 4E 2E 00 02 31 00 4E 2F 00 04 36 30 33 00 4E 30 00 00 4E 31 00 01 00 4E 33 00 00 4E 35 00 00 4E 36 00 01 0A 4E 37 00 01 06 4E 38 00 01 00 4E 3F 00 04 07 DD 0B 13 4E 40 00 0C 00 41 42 57 00 00 00 00 00 00 00 00 4E 41 00 02 08 04 4E 42 00 02 00 00 4E 43 00 02 0C 04 4E 45 00 01 05 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 06 4E 54 00 00 4E 5B 00 04 00 00 00 00 52 0B 00 04 13 80 02 00 52 0F 00 14 01 00 00 00 00 00 00 00 52 00 40 48 89 50 80 02 00 00 03 00 5D C2 00 0C 00 41 42 57 00 00 00 00 00 00 00 00 5D C8 00 00 65 97 00 01 07 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 01 A4 91 00 02 00 00 A4 93 00 02 00 01 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00".hexToBytes() + .read { + readTLVMap(tagSize = 2, expectingEOF = true) + } + val mapMale = + "4E 22 00 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 4E 25 00 06 34 33 33 31 30 30 4E 26 00 09 E4 B8 8D E7 9F A5 E9 81 93 4E 27 00 0A 31 33 38 2A 2A 2A 2A 2A 2A 2A 4E 29 00 01 01 4E 2A 00 00 4E 2B 00 00 4E 2D 00 23 68 74 74 70 3A 2F 2F 77 77 77 2E 34 33 39 39 2E 63 6F 6D 2F 66 6C 61 73 68 2F 33 32 39 37 39 2E 68 74 6D 4E 2E 00 02 31 00 4E 2F 00 04 36 30 33 00 4E 30 00 00 4E 31 00 01 00 4E 33 00 00 4E 35 00 00 4E 36 00 01 0A 4E 37 00 01 06 4E 38 00 01 00 4E 3F 00 04 07 DD 0B 13 4E 40 00 0C 00 41 42 57 00 00 00 00 00 00 00 00 4E 41 00 02 08 04 4E 42 00 02 00 00 4E 43 00 02 0C 04 4E 45 00 01 05 4E 49 00 04 00 00 00 00 4E 4B 00 04 00 00 00 00 4E 4F 00 01 06 4E 54 00 00 4E 5B 00 04 00 00 00 00 52 0B 00 04 13 80 02 00 52 0F 00 14 01 00 00 00 00 00 00 00 52 00 40 48 89 50 80 02 00 00 03 00 5D C2 00 0C 00 41 42 57 00 00 00 00 00 00 00 00 5D C8 00 00 65 97 00 01 07 69 9D 00 04 00 00 00 00 69 A9 00 00 9D A5 00 02 00 01 A4 91 00 02 00 00 A4 93 00 02 00 01 A4 94 00 02 00 00 A4 9C 00 02 00 00 A4 B5 00 02 00 00".hexToBytes() + .read { + readTLVMap(tagSize = 2, expectingEOF = true) + } + + mapFemale.filter { (key, value) -> !mapMale.containsKey(key) || !mapMale[key]!!.contentEquals(value) }.forEach { + println("id=" + it.key.toUShort().toUHexString() + ", valueFemale=" + it.value.toUHexString() + ",valueMale=" + mapMale[it.key]!!.toUHexString()) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/ProfilePicture.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/ProfilePicture.kt deleted file mode 100644 index 88d443495..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/action/ProfilePicture.kt +++ /dev/null @@ -1,16 +0,0 @@ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS") - -package net.mamoe.mirai.network.protocol.tim.packet.action - -import net.mamoe.mirai.network.protocol.tim.packet.* - -// 用户资料的头像 -/** - * 请求获取头像 - */ -@AnnotatedId(KnownPacketId.REQUEST_PROFILE) -object RequestProfilePicturePacket : OutgoingPacketBuilder { - operator fun invoke(): OutgoingPacket = buildOutgoingPacket { - - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEventPackets.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEventPackets.kt index bce9f87b3..9db1ed102 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEventPackets.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/event/MessageEventPackets.kt @@ -12,8 +12,8 @@ import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.io.printTLVMap import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.readLVByteArray import net.mamoe.mirai.utils.io.readTLVMap +import net.mamoe.mirai.utils.io.readUShortLVByteArray import kotlin.properties.Delegates @@ -43,13 +43,13 @@ class GroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventPacketI qq = readUInt() discardExact(48) - readLVByteArray() + readUShortLVByteArray() discardExact(2)//2个0x00 message = readMessageChain() val map = readTLVMap(true) - if (map.containsKey(18)) { - map.getValue(18).read { + if (map.containsKey(18u)) { + map.getValue(18u).read { val tlv = readTLVMap(true) //tlv.printTLVMap("消息结尾 tag=18 的 TLV") ////群主的18: 05 00 04 00 00 00 03 08 00 04 00 00 00 04 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08 @@ -61,7 +61,7 @@ class GroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventPacketI // 没有4, 群员 // 4=10, 管理员 - senderPermission = when (tlv.takeIf { it.containsKey(0x04) }?.get(0x04)?.getOrNull(3)?.toUInt()) { + senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) { null -> SenderPermission.MEMBER 0x08u -> SenderPermission.OWNER 0x10u -> SenderPermission.OPERATOR @@ -73,8 +73,8 @@ class GroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventPacketI } senderName = when { - tlv.containsKey(0x01) -> kotlinx.io.core.String(tlv.getValue(0x01))//这个人的qq昵称 - tlv.containsKey(0x02) -> kotlinx.io.core.String(tlv.getValue(0x02))//这个人的群名片 + tlv.containsKey(0x01u) -> kotlinx.io.core.String(tlv.getValue(0x01u))//这个人的qq昵称 + tlv.containsKey(0x02u) -> kotlinx.io.core.String(tlv.getValue(0x02u))//这个人的群名片 else -> { tlv.printTLVMap("TLV(tag=18) Map") MiraiLogger.warning("Could not determine senderName") @@ -113,7 +113,7 @@ class FriendMessageEventPacket(input: ByteReadPacket, eventIdentity: EventPacket //java.io.EOFException: Only 49 bytes were discarded of 69 requested //抖动窗口消息 discardExact(69) - readLVByteArray()//font + readUShortLVByteArray()//font discardExact(2)//2个0x00 message = readMessageChain() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt index c245c5439..91ffab2fa 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/protocol/tim/packet/login/ServerLoginResponse.kt @@ -3,6 +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.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.utils.Tested @@ -50,11 +51,6 @@ class LoginResponseKeyExchangeResponsePacket(input: ByteReadPacket) : ServerLogi } } -enum class Gender(val id: Boolean) { - MALE(false), - FEMALE(true); -} - @AnnotatedId(KnownPacketId.LOGIN) class LoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) { lateinit var sessionResponseDecryptionKey: IoBuffer//16 bytes| @@ -119,7 +115,6 @@ class LoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginResponsePac class Encrypted(input: ByteReadPacket) : ServerPacket(input) { fun decrypt(privateKey: ByteArray): LoginResponseSuccessPacket = LoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).applySequence(sequenceId) } - } /** diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt index fc41eee10..9ae36ad26 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt @@ -35,27 +35,30 @@ class GentleImage { lateinit var contact: Contact + // Deferred 将导致 kotlin 内部错误 val image: Deferred by lazy { GlobalScope.async { - delay((Math.random() * 5000L).toLong()) - withContext(Dispatchers.IO) { - class Result { - var id: String = "" + //delay((Math.random() * 5000L).toLong()) + class Result { + var id: String = "" + } + + withTimeoutOrNull(5 * 1000) { + withContext(Dispatchers.IO) { + val result = JSON.parseObject( + Jsoup.connect("http://dev.itxtech.org:10322/v2/randomImg.uue").ignoreContentType(true).timeout(10_0000).get().body().text(), + Result::class.java + ) + + Jsoup.connect("http://dev.itxtech.org:10322/img.uue?size=large&id=${result.id}") + .userAgent(UserAgent.randomUserAgent) + .timeout(10_0000) + .ignoreContentType(true) + .maxBodySize(Int.MAX_VALUE) + .execute() + .bodyStream() } - - val result = JSON.parseObject( - Jsoup.connect("http://dev.itxtech.org:10322/v2/randomImg.uue").ignoreContentType(true).get().body().text(), - Result::class.java - ) - - Jsoup.connect("http://dev.itxtech.org:10322/img.uue?size=large&id=${result.id}") - .userAgent(UserAgent.randomUserAgent) - .timeout(20_0000) - .ignoreContentType(true) - .maxBodySize(Int.MAX_VALUE) - .execute() - .bodyStream() - }.upload(contact) + }?.upload(contact) ?: error("Unable to download image") } } diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt index 60c883bef..3a657dc5a 100644 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt +++ b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt @@ -2,9 +2,9 @@ package demo.gentleman -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import net.mamoe.mirai.Bot import net.mamoe.mirai.BotAccount import net.mamoe.mirai.event.Event @@ -13,6 +13,7 @@ import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.login import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess import java.io.File +import kotlin.random.Random private fun readTestAccount(): BotAccount? { val file = File("testAccount.txt") @@ -46,6 +47,9 @@ suspend fun main() { bot.subscribeMessages { "你好" reply "你好!" + "profile" reply { + sender.profile.await().toString() + } /* has { @@ -53,16 +57,15 @@ suspend fun main() { }*/ startsWith("随机图片", removePrefix = true) { - withContext(Dispatchers.Default) { - try { - repeat(it.toIntOrNull() ?: 1) { - launch { - Gentlemen.provide(subject).receive().image.await().send() - } + try { + repeat(it.toIntOrNull() ?: 1) { + GlobalScope.launch { + delay(Random.Default.nextLong(100, 1000)) + Gentlemen.provide(subject).receive().image.await().send() } - } catch (e: Exception) { - reply(e.message ?: "exception: null") } + } catch (e: Exception) { + reply(e.message ?: "exception: null") } } diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Utils.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Utils.kt deleted file mode 100644 index 447d39f5c..000000000 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Utils.kt +++ /dev/null @@ -1,42 +0,0 @@ -package demo.gentleman - -import kotlin.reflect.KClass - - -@Throws(TryFailedException::class) -inline fun tryNTimes( - tryTimes: Int, - vararg expectingExceptions: KClass = Array>(1) { Exception::class }, - expectingHandler: (Exception) -> Unit = { }, - unexpectingHandler: (Exception) -> Unit = { throw it }, - block: () -> T -): T { - require(tryTimes > 0) { "tryTimes must be greater than 0" } - - var lastE: java.lang.Exception? = null - repeat(tryTimes) { - try { - return block() - } catch (e: Exception) { - if (lastE != null && lastE !== e) { - e.addSuppressed(lastE) - } - lastE = e - if (expectingExceptions.any { it.isInstance(e) }) { - expectingHandler(e) - } else { - unexpectingHandler(e) - } - } - } - - if (lastE != null) { - throw TryFailedException(lastE!!) - } - throw TryFailedException() -} - -class TryFailedException : RuntimeException { - constructor() : super() - constructor(e: Exception) : super(e) -} \ No newline at end of file