mirror of
https://github.com/mamoe/mirai.git
synced 2025-04-25 04:50:26 +08:00
Improve performance
This commit is contained in:
parent
735b0934b6
commit
8353a6ae8d
README.md
mirai-console/src/main/java/net/mamoe/mirai
mirai-core/src
commonMain/kotlin/net.mamoe.mirai
jvmMain/kotlin/net/mamoe/mirai/utils
mirai-debug/src/main/java
mirai-demos/mirai-demo-1/src/main/java/demo1
10
README.md
10
README.md
@ -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)
|
||||
|
||||
## 使用方法
|
||||
### 要求
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
@ -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() }
|
||||
}
|
||||
|
@ -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 }
|
||||
}
|
||||
|
||||
|
@ -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 -> {
|
||||
|
@ -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")
|
||||
}
|
@ -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
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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.*
|
||||
|
||||
|
||||
|
@ -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
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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)
|
||||
|
@ -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) }
|
53
mirai-debug/src/main/java/DecryptTest.kt
Normal file
53
mirai-debug/src/main/java/DecryptTest.kt
Normal 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()
|
||||
}
|
@ -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()
|
||||
}
|
||||
/*
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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>()
|
||||
|
Loading…
Reference in New Issue
Block a user