1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-25 04:50:26 +08:00

Improve performance

This commit is contained in:
Him188 2019-10-19 11:59:08 +08:00
parent 735b0934b6
commit 8353a6ae8d
32 changed files with 597 additions and 459 deletions

View File

@ -63,9 +63,13 @@ subscribe<FriendMessageEvent>{
机器人可以转发图片消息.详情查看 [Image.kt](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/Message.kt#L81)
## 现已支持
- 发送好友/群消息(2019/10/14)
- 接收好友/群消息, 消息链解析(2019/10/14)
- 好友在线状态改变(2019/10/14)
- 发送好友/群消息(10/14)
- 接受解析好友消息(10/14)
- 接收解析群消息(10/14)
- 成员权限, 昵称(10/18)
- 好友在线状态改变(10/14)
- Android客户端上线/下线(10/18)
## 使用方法
### 要求

View File

@ -175,7 +175,7 @@ object MiraiServer {
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), MiraiLogger)
if (runBlocking { bot.login() } === LoginResult.SUCCESS) {
bot.green("Login succeed")
bot.logGreen("Login succeed")
return bot
}
}

View File

@ -9,10 +9,8 @@ import net.mamoe.mirai.contact.groupIdToNumber
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.ContactList
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmOverloads
/**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
@ -61,9 +59,14 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
* [关闭][BotNetworkHandler.close]网络处理器, 取消所有运行在 [BotNetworkHandler.NetworkScope] 下的协程.
* 然后重新启动并尝试登录
*/
suspend fun reinitializeNetworkHandler(configuration: LoginConfiguration): LoginResult {
@JvmOverloads
suspend fun reinitializeNetworkHandler(configuration: BotNetworkConfiguration, cause: Throwable? = null): LoginResult {
logger.logPurple("Reinitializing BotNetworkHandler")
network.close()
try {
network.close(cause)
} catch (e: Exception) {
e.log()
}
network = TIMBotNetworkHandler(this)
return network.login(configuration)
}

View File

@ -8,8 +8,8 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotNetworkConfiguration
import net.mamoe.mirai.utils.ContactList
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.toUHexString
/**
@ -36,31 +36,31 @@ val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
//NetworkHandler
suspend fun Bot.sendPacket(packet: ClientPacket) = this.network.sendPacket(packet)
suspend fun Bot.login(configuration: LoginConfiguration.() -> Unit): LoginResult = this.network.login(LoginConfiguration().apply(configuration))
suspend fun Bot.login(configuration: BotNetworkConfiguration.() -> Unit): LoginResult = this.network.login(BotNetworkConfiguration().apply(configuration))
suspend fun Bot.login(): LoginResult = this.network.login(LoginConfiguration.Default)
suspend fun Bot.login(): LoginResult = this.network.login(BotNetworkConfiguration.Default)
//BotAccount
val Bot.qqAccount: Long get() = this.account.account
//logging
fun Bot.log(o: Any?) = info(o)
fun Bot.log(o: Any?) = logInfo(o)
fun Bot.println(o: Any?) = info(o)
fun Bot.info(o: Any?) = this.logger.logInfo(o)
fun Bot.println(o: Any?) = logInfo(o)
fun Bot.logInfo(o: Any?) = this.logger.logInfo(o)
fun Bot.error(o: Any?) = this.logger.logError(o)
fun Bot.logError(o: Any?) = this.logger.logError(o)
fun Bot.purple(o: Any?) = this.logger.logPurple(o)
fun Bot.logPurple(o: Any?) = this.logger.logPurple(o)
fun Bot.cyan(o: Any?) = this.logger.logCyan(o)
fun Bot.green(o: Any?) = this.logger.logGreen(o)
fun Bot.logCyan(o: Any?) = this.logger.logCyan(o)
fun Bot.logGreen(o: Any?) = this.logger.logGreen(o)
fun Bot.debug(o: Any?) = this.logger.logDebug(o)
fun Bot.logDebug(o: Any?) = this.logger.logDebug(o)
fun Bot.printPacketDebugging(packet: ServerPacket) {
debug("Packet=$packet")
debug("Packet size=" + packet.input.readBytes().size)
debug("Packet data=" + packet.input.readBytes().toUHexString())
logDebug("Packet=$packet")
logDebug("Packet size=" + packet.input.readBytes().size)
logDebug("Packet data=" + packet.input.readBytes().toUHexString())
}

View File

@ -51,16 +51,21 @@ interface Cancellable {
}
/**
* 广播一个事件的唯一途径
* 广播一个事件的唯一途径.
* [context] 不包含 [CoroutineExceptionHandler], 将会使用默认的异常捕获, [log]
* 也就是说, 这个方法不会抛出异常, 只会把异常交由 [context] 捕获
*/
@Suppress("UNCHECKED_CAST")
@JvmOverloads
suspend fun <E : Event> E.broadcast(context: CoroutineContext? = null): E {
suspend fun <E : Event> E.broadcast(context: CoroutineContext? = CoroutineExceptionHandler { _, e -> e.log() }): E {
var ctx = EventScope.coroutineContext
if (context != null) {
if (context[CoroutineExceptionHandler] == null)
ctx += CoroutineExceptionHandler { _, e -> e.log() }
if (context == null) {
ctx += CoroutineExceptionHandler { _, e -> e.log() }
} else {
ctx += context
if (context[CoroutineExceptionHandler] == null) {
ctx += CoroutineExceptionHandler { _, e -> e.log() }
}
}
return withContext(ctx) { this@broadcast.broadcastInternal() }
}

View File

@ -58,6 +58,7 @@ internal object EventListenerManger {
@Suppress("UNCHECKED_CAST")
internal suspend fun <E : Event> E.broadcastInternal(): E {
suspend fun callListeners(listeners: EventListeners<in E>) = listeners.lock.withLock {
//fixme 这个锁会导致在事件处理时再监听这个事件死锁
listeners.removeIfInlined { it.onEvent(this) == ListeningStatus.STOPPED }
}

View File

@ -18,24 +18,30 @@ internal fun IoBuffer.parseMessageFace(): Face {
}
internal fun IoBuffer.parsePlainText(): PlainText {
discardExact(1)
discardExact(1)//0x01
return PlainText(readLVString())
}
internal fun IoBuffer.parseLongText0x19(): PlainText {
//01 00 59 AA 02 56 30 01 3A 40 6E 35 46 4F 62 68 75 4B 6F 65 31 4E 63 45 41 6B 77 4B 51 5A 5A 4C 47 54 57 43 68 30 4B 56 7A 57 44 38 67 58 70 37 62 77 6A 67 51 69 66 66 53 4A 63 4F 69 78 4F 75 37 36 49 49 4F 37 48 32 55 63 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 14 01 75 01 01 6B 01 78 9C CD 92 BB 4E C3 30 14 86 77 9E C2 32 73 DA A4 21 24 48 4E AA F4 06 A5 B4 51 55 A0 A8 0B 4A 5D 27 35 E4 82 72 69 4B B7 6E 08 06 C4 C0 06 42 48 30 20 21 60 62 EB E3 34 F4 31 70 4A 11 23 23 FC 96 2C F9 D8 BF CF F1 77 8C F2 23 D7 01 03 12 84 D4 F7 54 28 64 78 08 88 87 FD 1E F5 6C 15 C6 91 C5 29 30 AF AD 00 26 E4 86 36 E8 06 94 58 2A CC FC 73 41 E0 1E 5A D4 21 0D D3 25 2A 2C 55 0A 1B D2 BA 5E E0 24 91 D7 B9 B5 72 41 E1 74 B9 5C E0 78 25 27 8B 92 28 14 45 45 FF 76 B4 E8 98 39 18 05 13 47 0B 24 03 4A 86 F5 D8 89 68 3D B4 21 B0 1C 93 71 11 21 08 49 30 A0 98 54 4B 6C 25 A5 E6 80 84 B4 A7 42 4F AA 18 DD 7E 5C F3 89 D0 C0 65 FD 78 58 6B 76 3A 3B 9B BB ED 62 9F AF ED 8F DB 25 C5 3E 38 91 BB C3 23 BB 49 2D AB B5 8D 0D 3A 32 62 79 BD 5A 35 E4 AD DC 1E 86 40 03 88 46 C4 05 8E 79 EA C7 11 EB 09 64 91 88 46 0E D1 C0 5F 73 FD 4D 00 65 97 95 02 D4 0F 34 94 65 D3 B2 78 80 7D C7 0F 54 B8 AA F0 E9 60 8F 4A EE 1E 3F 6E 2E 84 E4 F6 7E 3E 7D 9E 5D 5E 25 EF 67 C9 E4 15 FC DC 81 B2 29 08 0D 85 7E 1C 60 02 BC 45 33 E7 93 F3 D9 C3 D3 FC E5 6D 36 BD 86 2C C3 D7 66 7A 98 FD 4F ED 13 9B C7 C1 78 02 00 04 00 00 00 23 0E 00 07 01 00 04 00 00 00 09
discardExact(1)//0x01
val raw = readLVByteArray()
println("parseLongText0x19.raw=${raw.toUHexString()}")
return PlainText(raw.toUHexString())
}
internal fun IoBuffer.parseMessageImage0x06(): Image {
discardExact(1)
with(this.debugPrint("好友的图片")) {
//MiraiLogger.logDebug(this.toUHexString())
val filenameLength = readShort()
val suffix = readString(filenameLength).substringAfter(".")
discardExact(8)//03 00 04 00 00 02 9C 04
val length = readShort()//27
discardExact(1)
return Image("{${readString(length - 2/*去掉首尾各一个*/)}}.$suffix")
}
//MiraiLogger.logDebug(this.toUHexString())
val filenameLength = readShort()
val suffix = readString(filenameLength).substringAfter(".")
discardExact(8)//03 00 04 00 00 02 9C 04
val length = readShort()//=27
discardExact(1)//无意义符号?
return Image("{${readString(length - 2/*去掉首尾各一个无意义符号*/)}}.$suffix")
}
//00 1B filenameLength
// 43 37 46 29 5F 34 32 34 4E 33 55 37 7B 4C 47 36 7D 4F 25 5A 51 58 51 2E 6A 70 67 get suffix
// 03 00 04 00 00 02 9C 04
@ -46,9 +52,7 @@ internal fun IoBuffer.parseMessageImage0x06(): Image {
// 00 63 16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 36 36 38 65 35 43 36 38 45 36 42 44 32 46 35 38 34 31 42 30 39 37 39 45 37 46 32 35 34 33 38 38 31 33 43 33 2E 6A 70 67 66 2F 32 65 37 61 65 33 36 66 2D 61 39 31 63 2D 34 31 32 39 2D 62 61 34 32 2D 37 65 30 31 32 39 37 37 35 63 63 38 41
fun main() {
println("f/".toByteArray().toUHexString())
println("16 20 20 39 39 31 30 20 38 38 31 43 42 20 20 20 20 20 20 20 36 36 38 65 35 43 36 38 45 36 42 44 32 46 35 38 34 31 42 30 39 37 39 45 37 46 32 35 34 33 38 38 31 33 43 33 2E 6A 70 67 66 2F 32 65 37 61 65 33 36 66 2D 61 39 31 63 2D 34 31 32 39 2D 62 61 34 32 2D 37 65 30 31 32 39 37 37 35 63 63 38 41"
.hexToBytes().stringOf())
println(".".repeat(1000))
}
internal fun IoBuffer.parseMessageImage0x03(): Image {
@ -56,49 +60,47 @@ internal fun IoBuffer.parseMessageImage0x03(): Image {
return Image(String(readLVByteArray()))
}
internal fun ByteReadPacket.parseMessageChain(): MessageChain {
return readMessageChain()
}
internal fun ByteReadPacket.readMessage(): Message? {
val messageType = this.readByte().toInt()
val sectionLength = this.readShort().toLong()//sectionLength: short
val sectionData = this.readIoBuffer(sectionLength.toInt())//use buffer instead
val sectionLength = this.readShort()
val sectionData = this.readIoBuffer(sectionLength.toInt())
return try {
when (messageType) {
//todo 在每个parse里面都 discard 了第一byte.
0x01 -> sectionData.parsePlainText()
0x02 -> sectionData.parseMessageFace()
0x03 -> sectionData.parseMessageImage0x03()
0x06 -> sectionData.parseMessageImage0x06()
0x19 -> {//未知, 可能是长文本?
0x19 -> {//长文本前一部分? 可能不是长文本, 总长度为 0x5C=92, body长度为0x59=89
//19 00 5C / 01 00 59 AA 02 56 30 01 3A 40 6E 35 46 4F 62 68 75 4B 6F 65 31 4E 63 45 41 6B 77 4B 51 5A 5A 4C 47 54 57 43 68 30 4B 56 7A 57 44 38 67 58 70 37 62 77 6A 67 51 69 66 66 53 4A 63 4F 69 78 4F 75 37 36 49 49 4F 37 48 32 55 63 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 14 01 75 01 01 6B 01 78 9C CD 92 BB 4E C3 30 14 86 77 9E C2 32 73 DA A4 21 24 48 4E AA F4 06 A5 B4 51 55 A0 A8 0B 4A 5D 27 35 E4 82 72 69 4B B7 6E 08 06 C4 C0 06 42 48 30 20 21 60 62 EB E3 34 F4 31 70 4A 11 23 23 FC 96 2C F9 D8 BF CF F1 77 8C F2 23 D7 01 03 12 84 D4 F7 54 28 64 78 08 88 87 FD 1E F5 6C 15 C6 91 C5 29 30 AF AD 00 26 E4 86 36 E8 06 94 58 2A CC FC 73 41 E0 1E 5A D4 21 0D D3 25 2A 2C 55 0A 1B D2 BA 5E E0 24 91 D7 B9 B5 72 41 E1 74 B9 5C E0 78 25 27 8B 92 28 14 45 45 FF 76 B4 E8 98 39 18 05 13 47 0B 24 03 4A 86 F5 D8 89 68 3D B4 21 B0 1C 93 71 11 21 08 49 30 A0 98 54 4B 6C 25 A5 E6 80 84 B4 A7 42 4F AA 18 DD 7E 5C F3 89 D0 C0 65 FD 78 58 6B 76 3A 3B 9B BB ED 62 9F AF ED 8F DB 25 C5 3E 38 91 BB C3 23 BB 49 2D AB B5 8D 0D 3A 32 62 79 BD 5A 35 E4 AD DC 1E 86 40 03 88 46 C4 05 8E 79 EA C7 11 EB 09 64 91 88 46 0E D1 C0 5F 73 FD 4D 00 65 97 95 02 D4 0F 34 94 65 D3 B2 78 80 7D C7 0F 54 B8 AA F0 E9 60 8F 4A EE 1E 3F 6E 2E 84 E4 F6 7E 3E 7D 9E 5D 5E 25 EF 67 C9 E4 15 FC DC 81 B2 29 08 0D 85 7E 1C 60 02 BC 45 33 E7 93 F3 D9 C3 D3 FC E5 6D 36 BD 86 2C C3 D7 66 7A 98 FD 4F ED 13 9B C7 C1 78 02 00 04 00 00 00 23 0E 00 07 01 00 04 00 00 00 09
//1000个 "." 被压缩为上面这条消息.
//bot手机自己跟自己发消息会出这个
//似乎手机发消息就会有这个?
//sectionData: 01 00 1C AA 02 19 08 00 88 01 00 9A 01 11 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00
// 01 00 1C AA 02 19 08 00 88 01 00 9A 01 11 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00
return null
sectionData.readBytes().debugPrint("sectionData")
return PlainText("[UNKNOWN(${this.readBytes().toUHexString()})]")
println()
val value = readLVByteArray()
//todo 未知压缩算法
PlainText(String(value))
//return null
// PlainText(String(GZip.uncompress( value)))
sectionData.parseLongText0x19()
}
0x14 -> {//长文本
0x14 -> {//长文本的后一部分? 总长度 0x0175=373, body长度=0x016B=363
//14 01 75 01 01 6B 01 78 9C CD 92 4D 4F C2 30 18 C7 EF 7E 8A A6 1E C9 64 83 B1 CD A4 1B E1 4D 19 8A C6 20 11 BC 98 39 3A A8 EE C5 74 1D 20 37 6E 46 0F C6 83 37 8D 31 D1 83 89 51 4F DE F8 38 4C 3E 86 1D 62 3C 7A D4 7F 93 26 7D DA 7F 9F A7 BF A7 28 3F F4 5C D0 C7 34 24 81 AF 43 69 45 84 00 FB 76 D0 21 7E 57 87 11 73 04 0D E6 8D 25 C0 85 BC B0 0B 0E 29 C1 8E 0E 57 FE B9 20 F0 0E 1C E2 E2 2D CB C3 3A D4 A4 D5 72 A9 58 58 13 24 AD A4 0A B2 22 E5 84 42 59 91 85 9C 52 C8 2A 62 46 14 65 59 FD 76 34 C8 88 3B 38 05 CB 66 73 24 7D 82 07 F5 C8 65 A4 1E 76 21 70 5C 8B 73 C9 42 10 62 DA 27 36 36 CB 7C 95 4B CC 14 87 A4 A3 C3 AA B4 CE 82 63 B5 46 54 8D A4 EC F6 20 F4 68 D8 21 C5 ED 7D B5 B9 A1 0D 46 4D 33 E5 8F 86 47 AD DA 6E 73 2F D3 3B 91 2B 55 53 A9 6C B6 76 AC 96 DD 20 38 DB 36 21 30 00 22 0C 7B C0 B5 4E 83 88 F1 9E 40 1E 61 84 B9 D8 00 7F CD F5 37 01 94 5E 54 0A 50 8F 1A 28 CD A7 45 F1 C0 0E DC 80 EA 70 59 13 93 C1 1F 15 DF 3D 7E DC 5C 48 F1 ED FD 6C F2 3C BD BC 8A DF CF E2 F1 2B F8 B9 03 A5 13 10 06 0A 83 88 DA 18 F8 F3 66 CE C6 E7 D3 87 A7 D9 CB DB 74 72 0D 79 86 AF CD E4 30 FF 9F C6 27 23 A2 C1 36 02 00 04 00 00 00 23 0E 00 07 01 00 04 00 00 00 09
//是否要用 sectionData.read?
discardExact(1)
val value = readLVByteArray()
println(value.size)
println(value.toUHexString())
println("0x14的未知压缩的data=" + value.toUHexString())
//todo 未知压缩算法
this.discardExact(7)//几个TLV
return PlainText(String(value))
//后面似乎还有一节?
//discardExact(7)//02 00 04 00 00 00 23
return PlainText(value.toUHexString())
}
0x0E -> {

View File

@ -3,14 +3,16 @@ package net.mamoe.mirai.network
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancelChildren
import kotlinx.io.core.Closeable
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.ClientHeartbeatPacket
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.BotNetworkConfiguration
import net.mamoe.mirai.utils.PlatformDatagramChannel
import kotlin.coroutines.ContinuationInterceptor
@ -30,7 +32,7 @@ import kotlin.coroutines.ContinuationInterceptor
* A BotNetworkHandler is used to connect with Tencent servers.
*/
@Suppress("PropertyName")
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> {
/**
* [BotNetworkHandler] 的协程作用域.
* 所有 [BotNetworkHandler] 的协程均启动在此作用域下.
@ -47,17 +49,20 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
val socket: Socket
/**
* 得到 [PacketHandler].
* `get(EventPacketHandler)` 返回 [EventPacketHandler]
* `get(ActionPacketHandler)` 返回 [ActionPacketHandler]
* `get(ActionPacketHandler)` 返回 [ActionPacketHandler].
*
* 这个方法在 [PacketHandlerList] 中实现
*/
operator fun <T : PacketHandler> get(key: PacketHandler.Key<T>): T
/**
* 依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
*/
suspend fun login(configuration: LoginConfiguration): LoginResult
suspend fun login(configuration: BotNetworkConfiguration): LoginResult
/**
* 添加一个临时包处理器, 并发送相应的包
@ -72,13 +77,8 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : Closeable {
*/
suspend fun sendPacket(packet: ClientPacket)
override fun close() {
fun close(cause: Throwable? = null) {
//todo check??
NetworkScope.coroutineContext[ContinuationInterceptor]!!.cancelChildren(HandlerClosedException())
NetworkScope.coroutineContext[ContinuationInterceptor]!!.cancelChildren(CancellationException("handler closed", cause))
}
}
/**
* [BotNetworkHandler] closed
*/
class HandlerClosedException : CancellationException("handler closed")
}

View File

@ -6,6 +6,7 @@ import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
@ -100,4 +101,6 @@ class BotSession(
suspend fun BotSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet)
val BotSession.isOpen: Boolean get() = socket.isOpen
val BotSession.account: Long get() = bot.account.account
val BotSession.account: Long get() = bot.account.account
val <T : BotNetworkHandler<*>> T.session get() = this[ActionPacketHandler].session

View File

@ -17,10 +17,11 @@ import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.network.session
import net.mamoe.mirai.utils.*
/**
* [BotNetworkHandler] TIM PC 协议实现
*
@ -45,7 +46,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
temporaryPacketHandler.send(this[ActionPacketHandler].session)
}
override suspend fun login(configuration: LoginConfiguration): LoginResult {
override suspend fun login(configuration: BotNetworkConfiguration): LoginResult {
TIMProtocol.SERVER_IP.forEach {
bot.logger.logInfo("Connecting server $it")
this.socket = BotSocketAdapter(it, configuration)
@ -79,8 +80,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
private lateinit var sessionKey: ByteArray
override fun close() {
super.close()
override fun close(cause: Throwable?) {
super.close(cause)
this.heartbeatJob?.cancel(CancellationException("handler closed"))
this.heartbeatJob = null
@ -98,7 +99,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override suspend fun sendPacket(packet: ClientPacket) = socket.sendPacket(packet)
internal inner class BotSocketAdapter(override val serverIp: String, val configuration: LoginConfiguration) : DataPacketSocketAdapter {
internal inner class BotSocketAdapter(override val serverIp: String, val configuration: BotNetworkConfiguration) : DataPacketSocketAdapter {
override val channel: PlatformDatagramChannel = PlatformDatagramChannel(serverIp, 8000)
override val isOpen: Boolean get() = channel.isOpen
@ -140,7 +141,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
internal suspend fun resendTouch(): LoginResult {
if (::loginHandler.isInitialized) loginHandler.close()
loginHandler = LoginHandler()
loginHandler = LoginHandler(configuration)
val expect = expectPacket<ServerTouchResponsePacket>()
@ -187,7 +188,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
val name = packet::class.simpleName
if (name != null && !name.endsWith("Encrypted") && !name.endsWith("Raw")) {
bot.cyan("Packet received: $packet")
bot.logCyan("Packet received: $packet")
}
if (packet is ServerEventPacket) {
@ -240,14 +241,14 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
channel.send(buffer)//JVM: withContext(IO)
} catch (e: SendPacketInternalException) {
bot.logger.logError("Caught SendPacketInternalException: ${e.cause?.message}")
bot.reinitializeNetworkHandler(configuration)
bot.reinitializeNetworkHandler(configuration, e)
return@withContext
} finally {
buffer.release(IoBuffer.Pool)
}
}
bot.green("Packet sent: $packet")
bot.logGreen("Packet sent: $packet")
EventScope.launch { PacketSentEvent(bot, packet).broadcast() }
}
@ -264,7 +265,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
/**
* 处理登录过程
*/
inner class LoginHandler {
inner class LoginHandler(private val configuration: BotNetworkConfiguration) {
private lateinit var token00BA: ByteArray
private lateinit var token0825: ByteArray//56
private var loginTime: Int = 0
@ -350,7 +351,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
is ServerCaptchaTransmissionPacket -> {
//packet is ServerCaptchaWrongPacket
if (this.captchaSectionId == 0) {
bot.error("验证码错误, 请重新输入")
bot.logError("验证码错误, 请重新输入")
this.captchaSectionId = 1
this.captchaCache = null
}
@ -402,8 +403,18 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
heartbeatJob = NetworkScope.launch {
while (socket.isOpen) {
delay(90000)
socket.sendPacket(ClientHeartbeatPacket(bot.qqAccount, sessionKey))
delay(configuration.heartbeatPeriodMillis)
with(session) {
class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeoutMillis) {
ClientHeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<ServerHeartbeatResponsePacket> {}
} == null) {
bot.logPurple("Heartbeat timed out")
bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException())
return@launch
}
}
}
}
@ -447,7 +458,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
fun close() {
this.captchaCache = null
if (::sessionResponseDecryptionKey.isInitialized) this.sessionResponseDecryptionKey.release(IoBuffer.Pool)
if (::sessionResponseDecryptionKey.isInitialized && sessionResponseDecryptionKey.readRemaining != 0)
this.sessionResponseDecryptionKey.release(IoBuffer.Pool)
}
}
}

View File

@ -13,6 +13,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.AddFriendResult
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientCanAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket

View File

@ -10,7 +10,6 @@ import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.distributePacket
import net.mamoe.mirai.network.protocol.tim.packet.IgnoredServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerFriendOnlineStatusChangedPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientSendFriendMessagePacket

View File

@ -33,15 +33,4 @@ fun <T : PacketHandler> T.asNode(key: PacketHandler.Key<T>): PacketHandlerNode<T
open class PacketHandlerList : MutableList<PacketHandlerNode<*>> by mutableListOf() {
@Suppress("UNCHECKED_CAST")
operator fun <T : PacketHandler> get(key: PacketHandler.Key<T>): T = this.first { it.key === key }.instance as T
operator fun <T : PacketHandler> get(clazz: KClass<T>): T {
this.forEach {
if (it.clazz == clazz) {
@Suppress("UNCHECKED_CAST")
return@get it.instance as T
}
}
throw NoSuchElementException()
}
}

View File

@ -20,8 +20,6 @@ class ClientSendFriendMessagePacket(
private val message: MessageChain
) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeRandom(2)
writeQQ(botQQ)
writeHex(TIMProtocol.fixVer2)

View File

@ -1,8 +1,6 @@
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.packet.EventPacketIdentity
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
/**

View File

@ -4,8 +4,6 @@ package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.network.protocol.tim.packet.EventPacketIdentity
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.utils.readString

View File

@ -7,9 +7,7 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.packet.EventPacketIdentity
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.utils.printTLVMap
import net.mamoe.mirai.utils.printStringFromHex
import net.mamoe.mirai.utils.read
import net.mamoe.mirai.utils.readLVByteArray
import net.mamoe.mirai.utils.readTLVMap
@ -64,6 +62,7 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP
else -> error("Could not determine member permission, unknown tlv(key=0x04,value=$value0x04)")
}
}
0x01u -> SenderPermission.MEMBER
else -> error("Could not determine member permission, unknown tlv(key=0x03,value=$value0x03)")
}
@ -78,6 +77,10 @@ class ServerGroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventP
}
}
fun main() {
"00 00 00 08 00 0A 00 04 01 00 00 00 35 DB 60 A2 01 8D 62 3A B8 02 76 E4 B8 DD 06 B4 B4 BD A8 D5 DF 00 30 34 46 30 41 39 37 31 45 42 37 46 31 42 34 43 33 34 41 31 42 34 33 37 41 35 33 45 31 36 43 30 43 38 35 43 36 44 31 34 46 35 44 31 41 31 43 39 34".printStringFromHex()
}
//
//以前的消息: 00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD 58 2C 60 86 35 3A 30 B3 C7 63 4A 80 E7 CD 5B 64 00 0B 78 16 5D A3 0A FD 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A3 0A FD AB 77 16 02 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 04 01 00 01 36 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
//刚刚的消息: 00 00 00 2D 00 05 00 02 00 01 00 06 00 04 00 01 2E 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD 11 F4 B2 F2 1A E7 1F C4 F1 3F 23 FB 74 80 42 64 00 0B 78 1A 5D A3 26 C1 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A3 26 C1 AA 34 08 42 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
@ -106,8 +109,8 @@ class ServerFriendMessageEventPacket(input: ByteReadPacket, eventIdentity: Event
discardExact(2)//2个0x00
message = readMessageChain()
val map: Map<Int, ByteArray> = readTLVMap(true).withDefault { byteArrayOf() }
map.printTLVMap("readTLVMap")
//val map: Map<Int, ByteArray> = readTLVMap(true).withDefault { byteArrayOf() }
//map.printTLVMap("readTLVMap")
//println("map.getValue(18)=" + map.getValue(18).toUHexString())
//19 00 38 01 00 35 AA 02 32 50 03 60 00 68 00 9A 01 29 08 09 20 BF 02 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 B8 03 00 C0 03 00 D0 03 00 E8 03 00 12 00 25 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08 05 00 04 00 00 00 01 08 00 04 00 00 00 01

View File

@ -1,10 +1,13 @@
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.event.*
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.applySequence
import net.mamoe.mirai.network.protocol.tim.packet.decryptBy
import net.mamoe.mirai.utils.*
/**
@ -60,27 +63,33 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
0x0052u -> ServerGroupMessageEventPacket(input, eventIdentity)
0x00A6u -> ServerFriendMessageEventPacket(input.debugPrint("好友消息事件"), eventIdentity)
0x00A6u -> ServerFriendMessageEventPacket(input.debugColorizedPrint("好友消息事件", ignoreUntilFirstConst = true), eventIdentity)
//00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 16 00 00 00 37 08 02 1A 12 08 95 02 10 90 04 40 98 E1 8C ED 05 48 AF 96 C3 A4 03 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 29 08 00 10 05 18 98 E1 8C ED 05 20 01 28 FF FF FF FF 0F 32 15 E5 AF B9 E6 96 B9 E6 AD A3 E5 9C A8 E8 BE 93 E5 85 A5 2E 2E 2E
//00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00
0x0210u -> {
discardExact(19)
println("type事件" + readUByte().toUInt())
//0210: 00 00 00 0E 00 08 00 02 00 01 00 0A 00 04 01 00 00 00 00 00 00 06 00 00 00 26 08 02 1A 02 08 49 0A 08 08 00 10 B2 DE 8C ED 05 0A 0C 08 A2 FF 8C F0 03 10 E4 A1 A7 ED 05 0A 0C 08 DD F1 92 B7 07 10 B1 DE 8C ED 05
// 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 16 00 00 00 37 08 02 1A 12 08 95 02 10 90 04 40 98 E1 8C ED 05 48 AF 96 C3 A4 03 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 1A 29 08 00 10 05 18 98 E1 8C ED 05 20 01 28 FF FF FF FF 0F 32 15 E5 AF B9 E6 96 B9 E6 AD A3 E5 9C A8 E8 BE 93 E5 85 A5 2E 2E 2E
// 00 00 00 08 00 0A 00 04 01 00 00 00 00 00 00 07 00 00 00
/*{
//todo 02 10 可能是发起会话? 在手机 QQ 打开一个公众号也有这个包; 取消关注公众号也会有这个包; 手机打开某人聊天界面也会有
0x0210u -> discardExact(19)
println("type事件" + readUByte().toUInt().toByteArray().toUHexString())
//todo 错了. 可能是 00 79 才是.
ServerFriendTypingCanceledPacket(input, eventIdentity)
/*
if (readUByte().toUInt() == 0x37u) ServerFriendTypingStartedPacket(input, eventIdentity)
else /*0x22*/ ServerFriendTypingCanceledPacket(input, eventIdentity)*/
}
0x0079u -> IgnoredServerEventPacket(type, input, eventIdentity)
}*/
0x0210u -> IgnoredServerEventPacket(
eventId = type.toByteArray(),
showData = true,
input = input,
eventIdentity = eventIdentity
)
//"02 10", "00 12" -> ServerUnknownEventPacket(input, eventIdentity)
else -> {
MiraiLogger.logDebug("UnknownEvent type = ${type.toInt().toByteArray().toUHexString()}")
else -> {//0x00 79u, 可能是正在输入的包
MiraiLogger.logDebug("UnknownEvent type = ${type.toByteArray().toUHexString()}")
UnknownServerEventPacket(input, eventIdentity)
}
}.applyId(id).applySequence(sequenceId)
@ -114,7 +123,15 @@ abstract class ServerEventPacket(input: ByteReadPacket, val eventIdentity: Event
* 00 79: 总是与 01 12 一起发生, 00 79 却没多大意义
*/
@Suppress("unused")
class IgnoredServerEventPacket(val eventId: UShort, input: ByteReadPacket, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, eventIdentity)
class IgnoredServerEventPacket(val eventId: ByteArray, private val showData: Boolean = false, input: ByteReadPacket, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, eventIdentity) {
override fun decode() {
if (showData) {
MiraiLogger.logDebug("IgnoredServerEventPacket data: " + this.input.readBytes().toUHexString())
} else {
this.input.discard()
}
}
}
/**
* Unknown event

View File

@ -1,8 +1,6 @@
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.packet.EventPacketIdentity
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
sealed class ServerFriendTypingPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, eventIdentity) {

View File

@ -78,10 +78,8 @@ private fun BytePacketBuilder.writePart1(
this.writeHex("00 06")//tag
this.writeHex("00 78")//length
if (tlv0006 != null) {
MiraiLogger.logDebug("tlv0006!=null")
this.writeFully(tlv0006)
} else {
MiraiLogger.logDebug("tlv0006==null")
this.writeTLV0006(qq, password, loginTime, loginIP, privateKey)
}
//fix

View File

@ -0,0 +1,35 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerTouchResponsePacket
/**
* 网络配置
*/
class BotNetworkConfiguration {
/**
* 等待 [ServerTouchResponsePacket] 的时间
*/
var touchTimeoutMillis: Long = 2000
/**
* 是否使用随机的设备名.
* 使用随机可以降低被封禁的风险, 但可能导致每次登录都需要输入验证码
* 当一台设备只登录少量账号时, 将此项设置为 `true` 可能更好.
*/
var randomDeviceName: Boolean = false
/**
* 心跳周期. 过长会导致被服务器断开连接.
*/
var heartbeatPeriodMillis: Long = 1 * 60 * 1000
/**
* 每次心跳时等待结果的时间.
* 一旦心跳超时, 整个网络服务将会重启 (将消耗约 1s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响.
*/
var heartbeatTimeoutMillis: Long = 2000
companion object {
val Default = BotNetworkConfiguration()
}
}

View File

@ -2,6 +2,8 @@
package net.mamoe.mirai.utils
import kotlinx.io.charsets.Charset
import kotlinx.io.charsets.Charsets
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlin.jvm.JvmOverloads
@ -18,7 +20,7 @@ fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(s
@JvmOverloads
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator)
fun ByteArray.stringOf(): String = String(this)
fun ByteArray.stringOfWitch(charset: Charset = Charsets.UTF_8): String = String(this, charset = charset)
//@JvmSynthetic TODO 等待 kotlin 修复 bug 后添加这个注解
@JvmOverloads

View File

@ -8,6 +8,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*

View File

@ -33,3 +33,25 @@ internal fun ByteReadPacket.debugPrint(name: String = ""): ByteReadPacket {
DebugLogger.logPurple("ByteReadPacket $name=" + bytes.toUHexString())
return bytes.toReadPacket()
}
@Deprecated("Low efficiency, only for debug purpose", ReplaceWith(""))
internal fun ByteReadPacket.debugColorizedPrint(name: String = "", ignoreUntilFirstConst: Boolean = false): ByteReadPacket {
val bytes = this.readBytes()
bytes.printColorizedHex(name, ignoreUntilFirstConst)
return bytes.toReadPacket()
}
internal fun String.printStringFromHex() {
println(this.hexToBytes().stringOfWitch())
}
internal fun ByteArray.printColorizedHex(name: String = "", ignoreUntilFirstConst: Boolean = false) {
println("Hex比较 `$name`")
println(toUHexString().colorize(ignoreUntilFirstConst))
println()
}
expect fun compareHex(hex1s: String, hex2s: String): String
expect fun String.colorize(ignoreUntilFirstConst: Boolean = false): String

View File

@ -1,18 +0,0 @@
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerTouchResponsePacket
class LoginConfiguration {
/**
* 等待 [ServerTouchResponsePacket] 的时间
*/
var touchTimeoutMillis: Long = 2000
var randomDeviceName: Boolean = false
companion object {
val Default = LoginConfiguration()
}
}

View File

@ -4,7 +4,6 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import kotlin.random.Random
import kotlin.random.nextInt
@ -25,9 +24,10 @@ fun BytePacketBuilder.writeLVByteArray(byteArray: ByteArray) {
fun BytePacketBuilder.writeLVPacket(packet: ByteReadPacket) {
this.writeShort(packet.remaining.toShort())
this.writePacket(packet)
packet.release()
}
fun BytePacketBuilder.writeLVPacket(builder: BytePacketBuilder.() -> Unit) = this.writeLVPacket(BytePacketBuilder().apply(builder).use { it.build() })
fun BytePacketBuilder.writeLVPacket(builder: BytePacketBuilder.() -> Unit) = this.writeLVPacket(BytePacketBuilder().apply(builder).build())
@Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVString(str: String) = this.writeLVByteArray(str.toByteArray())

View File

@ -12,21 +12,30 @@ import kotlin.random.nextInt
* 255 -> 00 00 00 FF
*/
fun Int.toByteArray(): ByteArray = byteArrayOf(
(this.ushr(24) and 0xFF).toByte(),
(this.ushr(16) and 0xFF).toByte(),
(this.ushr(8) and 0xFF).toByte(),
(this.ushr(0) and 0xFF).toByte()
(ushr(24) and 0xFF).toByte(),
(ushr(16) and 0xFF).toByte(),
(ushr(8) and 0xFF).toByte(),
(ushr(0) and 0xFF).toByte()
)
/**
* 255 -> 00 FF
*/
fun UShort.toByteArray(): ByteArray = with(toUInt()) {
byteArrayOf(
(shr(8) and 255u).toByte(),
(shr(0) and 255u).toByte()
)
}
/**
* 255u -> 00 00 00 FF
*/
fun UInt.toByteArray(): ByteArray = byteArrayOf(
(this.shr(24) and 255u).toByte(),
(this.shr(16) and 255u).toByte(),
(this.shr(8) and 255u).toByte(),
(this.shr(0) and 255u).toByte()
(shr(24) and 255u).toByte(),
(shr(16) and 255u).toByte(),
(shr(8) and 255u).toByte(),
(shr(0) and 255u).toByte()
)
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)

View File

@ -0,0 +1,292 @@
@file:Suppress("ObjectPropertyName", "MayBeConstant", "NonAsciiCharacters", "SpellCheckingInspection")
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import java.lang.reflect.Field
import java.util.*
import kotlin.math.max
/**
* Hex 比较器, 并着色已知常量
*
* This could be used to check packet encoding..
* but better to run under UNIX
*
* @author NaturalHG
* @author Him188moe
*/
internal object HexComparator {
private val RED = "\u001b[31m"
private val GREEN = "\u001b[33m"
private val UNKNOWN_COLOR = "\u001b[30m"
private val BLUE = "\u001b[34m"
@Suppress("unused")
class ConstMatcher constructor(hex: String) {
private val matches = LinkedList<Match>()
object TestConsts {
val NIU_BI = "牛逼".toByteArray().toUHexString()
val _1994701021 = 1994701021.toUHexString(" ")
val _1040400290 = 1040400290.toUHexString(" ")
val _580266363 = 580266363.toUHexString(" ")
val _1040400290_ = "3E 03 3F A2"
val _1994701021_ = "76 E4 B8 DD"
val _jiahua_ = "B1 89 BE 09"
val _Him188moe_ = "Him188moe".toByteArray().toUHexString()
val 发图片2 = "发图片2".toByteArray().toUHexString()
val 发图片群 = "发图片群".toByteArray().toUHexString()
val 发图片 = "发图片".toByteArray().toUHexString()
val = "".toByteArray().toUHexString()
val 你好 = "你好".toByteArray().toUHexString()
val SINGLE_PLAIN_MESSAGE_HEAD = "00 00 01 00 09 01"
val MESSAGE_TAIL_10404 = "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"
.replace(" ", " ")
val FONT_10404 = "E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
}
@Suppress("SpellCheckingInspection")
object PacketIds {
val heartbeat = "00 58"
val friendmsgsend = "00 CD"
val friendmsgevent = "00 CE"
}
init {
CONST_FIELDS.forEach { field ->
for (match in match(hex, field)) {
matches.add(Match(match, field.name))
}
}
}
fun getMatchedConstName(hexNumber: Int): String? {
for (match in this.matches) {
if (match.range.contains(hexNumber)) {
return match.constName
}
}
return null
}
private class Match internal constructor(val range: IntRange, val constName: String)
companion object {
private val CONST_FIELDS: List<Field> = listOf(
TestConsts::class.java,
TIMProtocol::class.java,
PacketIds::class.java
).map { it.declaredFields }.flatMap { fields ->
fields.map { field ->
field.trySetAccessible()
field
}
}
}
private fun match(hex: String, field: Field): List<IntRange> {
val constValue: String
try {
constValue = (field.get(null) as String).trim { it <= ' ' }
if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes
return LinkedList()
}
} catch (e: IllegalAccessException) {
throw RuntimeException(e)
} catch (ignored: ClassCastException) {
return LinkedList()
}
return object : LinkedList<IntRange>() {
init {
var index = -1
index = hex.indexOf(constValue, index + 1)
while (index != -1) {
add(IntRange(index / 3, (index + constValue.length) / 3))
index = hex.indexOf(constValue, index + 1)
}
}
}
}
}
private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) {
//System.out.println(constMatcher.matches);
var i = 0
while (i < length) {
constNameBuilder.append(" ")
val match = constMatcher.getMatchedConstName(i / 4)
if (match != null) {
var appendedNameLength = match.length
constNameBuilder.append(match)
while (match == constMatcher.getMatchedConstName(i++ / 4)) {
if (appendedNameLength-- < 0) {
constNameBuilder.append(" ")
}
}
constNameBuilder.append(" ".repeat(match.length % 4))
}
i++
}
}
fun compare(hex1s: String, hex2s: String): String {
val builder = StringBuilder()
val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(hex1s)
val constMatcher2 = ConstMatcher(hex2s)
if (hex1.size == hex2.size) {
builder.append(GREEN).append("长度一致:").append(hex1.size)
} else {
builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size)
}
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
val hex2b = StringBuilder()
val hex2ConstName = StringBuilder()
var dif = 0
val length = max(hex1.size, hex2.size) * 4
buildConstNameChain(length, constMatcher1, hex1ConstName)
buildConstNameChain(length, constMatcher2, hex2ConstName)
for (i in 0 until max(hex1.size, hex2.size)) {
var h1: String? = null
var h2: String? = null
var isDif = false
if (hex1.size <= i) {
h1 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
h1 = BLUE + hex1[i]
}
}
if (hex2.size <= i) {
h2 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher2.getMatchedConstName(i)
if (matchedConstName != null) {
h2 = BLUE + hex2[i]
}
}
if (h1 == null && h2 == null) {
h1 = hex1[i]
h2 = hex2[i]
if (h1 == h2) {
h1 = GREEN + h1
h2 = GREEN + h2
} else {
h1 = RED + h1
h2 = RED + h2
isDif = true
}
} else {
if (h1 == null) {
h1 = RED + hex1[i]
}
if (h2 == null) {
h2 = RED + hex2[i]
}
}
numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
hex2b.append(" ").append(h2).append(" ")
if (isDif) {
++dif
}
//doConstReplacement(hex1b);
//doConstReplacement(hex2b);
}
return builder.append(" ").append(dif).append(" 个不同").append("\n")
.append(numberLine).append("\n")
.append(hex1ConstName).append("\n")
.append(hex1b).append("\n")
.append(hex2b).append("\n")
.append(hex2ConstName).append("\n")
.toString()
}
fun String.colorize(ignoreUntilFirstConst: Boolean = false): String {
val builder = StringBuilder()
val hex = trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(this)
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
buildConstNameChain(length, constMatcher1, hex1ConstName)
var firstConst: String? = null
var constNameOffset = 0//已经因为还没到第一个const跳过了几个char
for (i in hex.indices) {
var h1: String? = null
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
firstConst = firstConst ?: matchedConstName
h1 = BLUE + hex[i]
}
if (!ignoreUntilFirstConst || firstConst != null) {//有过任意一个 Const
if (h1 == null) {
h1 = GREEN + hex[i]
}
numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
} else {
constNameOffset++
}
}
return builder.append("\n")
.append(numberLine).append("\n")
.append(if (firstConst == null) hex1ConstName else {
with(hex1ConstName) {
val index = indexOf(firstConst)
if (index == -1) toString() else " " + substring(index, length)
}
}).append("\n")
.append(hex1b).append("\n")
.toString()
}
private fun getFixedNumber(number: Int): String {
if (number < 10) {
return "00$number"
}
return if (number < 100) {
"0$number"
} else number.toString()
}
}
actual fun String.colorize(ignoreUntilFirstConst: Boolean): String = with(HexComparator) { colorize(ignoreUntilFirstConst) }
actual fun compareHex(hex1s: String, hex2s: String): String = with(HexComparator) { compare(hex1s, hex2s) }

View File

@ -0,0 +1,53 @@
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.stringOfWitch
import net.mamoe.mirai.utils.toUHexString
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.*
import java.util.zip.GZIPInputStream
fun main() {
val short: Short = 0x8b1f.toShort()
// 7字节相同| |18字节向东
val bytes = "AA 02 56 30 01 3A 40 53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00".hexToBytes()
println(bytes.stringOfWitch())
val string = bytes.stringOfWitch()
// println("53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64".hexToBytes().stringOfWitch())
println("53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64"
.hexToBytes().unbase64().stringOfWitch())
println("53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64"
.hexToBytes().unbase64().toUHexString())
//base64解密结果 48 A2 BF A1 8D C1 F7 F8 55 4C 42 9E 8D 9F 79 10 CC E1 67 FA 09 07 2E 7A 6A 83 D8 DA 74 08 C0 08 3B 19 0D F8 7A B0 8B 02 B3 CA 57 DE 42 BD EE 1D
println()
println()
println(Base64.getEncoder().encodeToString(".".repeat(1000).toByteArray()))
// 01 78
val data2 = "9C CD 92 BB 4E C3 30 14 86 77 9E C2 32 23 4A 73 69 D3 76 48 52 25 25 BD 48 84 56 54 15 97 05 85 C4 09 2E B9 A8 4E D2 92 6E DD 10 0C 88 81 0D 84 90 60 40 42 C0 C4 D6 C7 69 E8 63 E0 94 22 46 46 F8 2D 59 F2 B1 7F 9F E3 EF 58 AA 9D FA 1E 18 21 12 E1 30 90 21 5F E0 20 40 81 15 DA 38 70 65 98 C4 0E 53 85 35 65 0D 50 49 7E E4 82 23 82 91 23 C3 C2 3F 17 04 FE A1 83 3D B4 6D FA 48 86 42 45 2F F3 82 58 64 CA A2 2E 32 25 AD 51 66 54 AD 21 32 AA AA AB 1C 5F 15 04 AE 5E F9 76 F4 F0 84 3A 28 05 D3 8A 97 48 46 18 8D 8D C4 8B B1 11 B9 10 38 9E 49 B9 14 21 88 10 19 61 0B B5 37 E9 4A CC CD 04 45 D8 96 E1 38 ED B7 BA 9D 81 53 2F A5 7B A1 A8 B1 29 6E 69 EE C4 68 75 DB FE 46 C7 76 3B 27 BA 67 F4 93 DD 83 DE 7E 33 16 06 CD B2 C6 0F 35 42 D8 52 63 C7 DE 1A 0E 86 1A 04 0A 90 70 8C 7C E0 99 69 98 C4 B4 27 90 46 62 1C 7B 48 01 7F CD F5 37 01 89 5D 55 0A A4 63 A2 48 2C 9D 56 C5 03 2B F4 42 22 C3 F5 2A 97 0F FA A8 EC EE F1 E3 E6 82 CF 6E EF 17 B3 E7 F9 E5 55 F6 7E 96 4D 5F C1 CF 1D 12 9B 83 50 A4 28 4C 88 85 40 B0 6C E6 62 7A 3E 7F 78 5A BC BC CD 67 D7 90 66 F8 DA CC 0F D3 FF A9 7C 02 73 51 C1 65".replace(" ", " ").hexToBytes()
println(data2.size)
println(data2.unbase64().toUHexString())
}
fun ByteArray.unbase64() = Base64.getDecoder().decode(this)
fun ByteArray.ungzip(): ByteArray {
val out = ByteArrayOutputStream()
val `in` = ByteArrayInputStream(this)
val ungzip = GZIPInputStream(`in`)
val buffer = ByteArray(256)
var n: Int
while (ungzip.read(buffer).also { n = it } >= 0) {
out.write(buffer, 0, n)
}
return out.toByteArray()
}

View File

@ -1,298 +1,11 @@
@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant")
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.toUHexString
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
import java.lang.reflect.Field
import net.mamoe.mirai.utils.compareHex
import java.util.*
import kotlin.math.max
/**
* Hex 比较器, 并着色已知常量
*
* This could be used to check packet encoding..
* but better to run under UNIX
*
* @author NaturalHG
* @author Him188moe
*/
object HexComparator {
private val RED = "\u001b[31m"
private val GREEN = "\u001b[33m"
private val UNKNOWN = "\u001b[30m"
private val BLUE = "\u001b[34m"
private val clipboardString: String?
get() {
val trans = Toolkit.getDefaultToolkit().systemClipboard.getContents(null)
if (trans.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
return trans.getTransferData(DataFlavor.stringFlavor) as String
} catch (e: Exception) {
e.printStackTrace()
}
}
return null
}
class ConstMatcher constructor(hex: String) {
private val matches = LinkedList<Match>()
object TestConsts {
val NIU_BI = "牛逼".toByteArray().toUHexString()
val _1994701021 = 1994701021.toUHexString(" ")
val _1040400290 = 1040400290.toUHexString(" ")
val _580266363 = 580266363.toUHexString(" ")
val _1040400290_ = "3E 03 3F A2"
val _1994701021_ = "76 E4 B8 DD"
val _jiahua_ = "B1 89 BE 09"
val _Him188moe_ = "Him188moe".toByteArray().toUHexString()
val 发图片 = "发图片".toByteArray().toUHexString()
val = "".toByteArray().toUHexString()
val 你好 = "你好".toByteArray().toUHexString()
val SINGLE_PLAIN_MESSAGE_HEAD = "00 00 01 00 09 01"
val MESSAGE_TAIL_10404 = "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"
.replace(" ", " ")
}
@Suppress("SpellCheckingInspection")
object PacketIds {
val heartbeat = "00 58"
val friendmsgsend = "00 CD"
val friendmsgevent = "00 CE"
}
init {
CONST_FIELDS.forEach { field ->
for (match in match(hex, field)) {
matches.add(Match(match, field.name))
}
}
}
fun getMatchedConstName(hexNumber: Int): String? {
for (match in this.matches) {
if (match.range.contains(hexNumber)) {
return match.constName
}
}
return null
}
private class Match internal constructor(val range: IntRange, val constName: String)
companion object {
private val CONST_FIELDS: List<Field> = listOf(
TestConsts::class.java,
TIMProtocol::class.java,
PacketIds::class.java
).map { it.declaredFields }.flatMap { fields ->
fields.map { field ->
field.trySetAccessible()
field
}
}
}
private fun match(hex: String, field: Field): List<IntRange> {
val constValue: String
try {
constValue = (field.get(null) as String).trim { it <= ' ' }
if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes
return LinkedList()
}
} catch (e: IllegalAccessException) {
throw RuntimeException(e)
} catch (ignored: ClassCastException) {
return LinkedList()
}
return object : LinkedList<IntRange>() {
init {
var index = -1
index = hex.indexOf(constValue, index + 1)
while (index != -1) {
add(IntRange(index / 3, (index + constValue.length) / 3))
index = hex.indexOf(constValue, index + 1)
}
}
}
}
}
private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) {
//System.out.println(constMatcher.matches);
var i = 0
while (i < length) {
constNameBuilder.append(" ")
val match = constMatcher.getMatchedConstName(i / 4)
if (match != null) {
var appendedNameLength = match.length
constNameBuilder.append(match)
while (match == constMatcher.getMatchedConstName(i++ / 4)) {
if (appendedNameLength-- < 0) {
constNameBuilder.append(" ")
}
}
constNameBuilder.append(" ".repeat(match.length % 4))
}
i++
}
}
fun compare(hex1s: String, hex2s: String): String {
val builder = StringBuilder()
val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(hex1s)
val constMatcher2 = ConstMatcher(hex2s)
if (hex1.size == hex2.size) {
builder.append(GREEN).append("长度一致:").append(hex1.size)
} else {
builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size)
}
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
val hex2b = StringBuilder()
val hex2ConstName = StringBuilder()
var dif = 0
val length = max(hex1.size, hex2.size) * 4
buildConstNameChain(length, constMatcher1, hex1ConstName)
buildConstNameChain(length, constMatcher2, hex2ConstName)
for (i in 0 until max(hex1.size, hex2.size)) {
var h1: String? = null
var h2: String? = null
var isDif = false
if (hex1.size <= i) {
h1 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
h1 = BLUE + hex1[i]
}
}
if (hex2.size <= i) {
h2 = RED + "__"
isDif = true
} else {
val matchedConstName = constMatcher2.getMatchedConstName(i)
if (matchedConstName != null) {
h2 = BLUE + hex2[i]
}
}
if (h1 == null && h2 == null) {
h1 = hex1[i]
h2 = hex2[i]
if (h1 == h2) {
h1 = GREEN + h1
h2 = GREEN + h2
} else {
h1 = RED + h1
h2 = RED + h2
isDif = true
}
} else {
if (h1 == null) {
h1 = RED + hex1[i]
}
if (h2 == null) {
h2 = RED + hex2[i]
}
}
numberLine.append(UNKNOWN).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
hex2b.append(" ").append(h2).append(" ")
if (isDif) {
++dif
}
//doConstReplacement(hex1b);
//doConstReplacement(hex2b);
}
return builder.append(" ").append(dif).append(" 个不同").append("\n")
.append(numberLine).append("\n")
.append(hex1ConstName).append("\n")
.append(hex1b).append("\n")
.append(hex2b).append("\n")
.append(hex2ConstName).append("\n")
.toString()
}
fun colorize(hex1s: String): String {
val builder = StringBuilder()
val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(hex1s)
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
buildConstNameChain(hex1s.length, constMatcher1, hex1ConstName)
for (i in hex1.indices) {
var h1: String? = null
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
h1 = BLUE + hex1[i]
}
if (h1 == null) {
h1 = hex1[i]
h1 = GREEN + h1
}
numberLine.append(UNKNOWN).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
}
return builder.append("\n")
.append(numberLine).append("\n")
.append(hex1ConstName).append("\n")
.append(hex1b).append("\n")
.toString()
}
private fun getFixedNumber(number: Int): String {
if (number < 10) {
return "00$number"
}
return if (number < 100) {
"0$number"
} else number.toString()
}
}
fun main() {
// println(HexComparator.colorize("00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD E7 86 74 F2 64 55 AD 9A EB 2F B9 DF F1 7F 8C 28 00 0B 78 14 5D A2 F5 CB 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A2 F5 CA 9D 26 CB 5E 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00"))
val scanner = Scanner(System.`in`)
while (true) {
println("Hex1: ")
@ -300,7 +13,7 @@ fun main() {
println("Hex2: ")
val hex2 = scanner.nextLine()
println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
println(HexComparator.compare(hex1, hex2))
println(compareHex(hex1, hex2))
println()
}
/*

View File

@ -7,10 +7,10 @@ import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.UnknownServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.UnknownServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.event.UnknownServerEventPacket
import net.mamoe.mirai.utils.*
/**
@ -177,6 +177,6 @@ object Main {
}
fun main() {
println("00 01 00 23 24 8B 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".hexToBytes().stringOf())
println("00 01 00 23 24 8B 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".hexToBytes().stringOfWitch())
}

View File

@ -7,7 +7,6 @@ import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.subscribeAll
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeOnce
import net.mamoe.mirai.event.subscribeUntilFalse
import net.mamoe.mirai.login
import net.mamoe.mirai.message.Image
@ -20,8 +19,8 @@ import net.mamoe.mirai.utils.MiraiLogger
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(BotAccount(//填写你的账号
account = 1994701021,
password = "asdhim188666"
account = 1994701121,
password = "123456"
), Console())
bot.login {
@ -35,9 +34,10 @@ suspend fun main() {
}
//提供泛型以监听事件
subscribeOnce<FriendMessageEvent> {
subscribeAlways<FriendMessageEvent> {
//获取第一个纯文本消息, 获取不到会抛出 NoSuchElementException
val firstText = it.message.first<PlainText>()
//val firstText = it.message.first<PlainText>()
val firstText = it.message.firstOrNull<PlainText>()
//获取第一个图片
val firstImage = it.message.firstOrNull<Image>()