mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-03 15:10:14 +08:00
Profile suppport
This commit is contained in:
parent
849f5caf8d
commit
61b10db854
@ -33,6 +33,7 @@ Mirai 在 JVM 平台采用插件模式运行,同时提供独立的跨平台核
|
||||
- 上传并发送好友/群图片(10/21, 10/26)
|
||||
- 群员权限改变(11/2)
|
||||
- 发起会话(11/2)
|
||||
- 个人资料(11/2)
|
||||
|
||||
计划中: 添加好友
|
||||
|
||||
|
@ -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<Bot> = mutableListOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一个好友
|
||||
*/
|
||||
suspend fun ContactSystem.addFriend(id: UInt): Nothing = TODO()
|
@ -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<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
|
||||
*/
|
||||
sealed class Contact(val bot: Bot, val id: UInt) {
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息. 速度太快会被服务器拒绝(无响应)
|
||||
*/
|
||||
abstract suspend fun sendMessage(message: MessageChain)
|
||||
|
||||
|
||||
@ -102,11 +110,40 @@ 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) {
|
||||
val profile: Deferred<Profile> 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<RequestProfileDetailsPacket.Response, Profile> { it.profile }
|
||||
.await().let {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if ((::profile as SuspendLazy<Profile>).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;
|
||||
}
|
@ -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)
|
||||
class FriendOnlineStatusChangedEvent(bot: Bot, sender: QQ, val newStatus: OnlineStatus) : FriendEvent(bot, sender)
|
||||
|
||||
/**
|
||||
* 好友个人资料更新
|
||||
*/
|
||||
class FriendProfileUpdatedEvent(bot: Bot, qq: QQ, val profile: Profile) : FriendEvent(bot, qq)
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
}
|
||||
}
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -35,27 +35,30 @@ class GentleImage {
|
||||
|
||||
|
||||
lateinit var contact: Contact
|
||||
// Deferred<Image?> 将导致 kotlin 内部错误
|
||||
val image: Deferred<Image> 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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<Image> {
|
||||
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,42 +0,0 @@
|
||||
package demo.gentleman
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
@Throws(TryFailedException::class)
|
||||
inline fun <T> tryNTimes(
|
||||
tryTimes: Int,
|
||||
vararg expectingExceptions: KClass<out Exception> = Array<KClass<out Exception>>(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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user