Commit bd9cf3bf authored by Him188's avatar Him188

Redesign packets

parent 6f498554
package net.mamoe.mirai
actual object MiraiEnvironment
\ No newline at end of file
package net.mamoe.mirai.event
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
internal actual val EventDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
/**
* 包处理协程调度器.
*
* JVM: 独立的 4 thread 调度器
*/
actual val NetworkDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher()
\ No newline at end of file
package net.mamoe.mirai.utils
import android.util.Log
actual typealias PlatformLogger = AndroidLogger
/**
* Android 平台的默认的日志记录器, 使用 [Log]
* 不应该直接构造这个类的实例. 需使用 [DefaultLogger]
*/
open class AndroidLogger internal constructor(override val identity: String?) : MiraiLoggerPlatformBase() {
override fun verbose0(any: Any?) {
Log.v(identity, any.toString())
}
override fun verbose0(message: String?, e: Throwable?) {
Log.v(identity, message.toString(), e)
}
override fun debug0(any: Any?) {
Log.d(identity, any.toString())
}
override fun debug0(message: String?, e: Throwable?) {
Log.d(identity, message.toString(), e)
}
override fun info0(any: Any?) {
Log.i(identity, any.toString())
}
override fun info0(message: String?, e: Throwable?) {
Log.i(identity, message.toString(), e)
}
override fun warning0(any: Any?) {
Log.w(identity, any.toString())
}
override fun warning0(message: String?, e: Throwable?) {
Log.w(identity, message.toString(), e)
}
override fun error0(any: Any?) {
Log.e(identity, any.toString())
}
override fun error0(message: String?, e: Throwable?) {
Log.e(identity, message.toString(), e)
}
}
\ No newline at end of file
package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.ContentType
import io.ktor.http.content.OutgoingContent
import io.ktor.util.KtorExperimentalAPI
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.io.core.Input
import java.io.DataInput
import java.io.EOFException
import java.io.InputStream
import java.net.InetAddress
import java.security.MessageDigest
import java.util.zip.CRC32
/**
* 设备名
*/
actual val deviceName: String get() = InetAddress.getLocalHost().hostName
/**
* Ktor HttpClient. 不同平台使用不同引擎.
*/
@KtorExperimentalAPI
internal actual val httpClient: HttpClient
get() = HttpClient(CIO)
/**
* Localhost 解析
*/
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
internal actual fun HttpRequestBuilder.configureBody(
inputSize: Long,
input: Input
) {
body = object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType = ContentType.Image.PNG
override val contentLength: Long = inputSize
override suspend fun writeTo(channel: ByteWriteChannel) {//不知道为什么这个 channel 在 common 找不到...
val buffer = byteArrayOf(1)
repeat(contentLength.toInt()) {
input.readFully(buffer, 0, 1)
channel.writeFully(buffer, 0, 1)
}
}
}
}
/**
* MD5 算法
*
* @return 16 bytes
*/
actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
fun InputStream.md5(): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
this.readInSequence {
digest.update(it.toByte())
}
return digest.digest()
}
fun DataInput.md5(): ByteArray {
val digest = MessageDigest.getInstance("md5")
digest.reset()
val buffer = byteArrayOf(1)
while (true) {
try {
this.readFully(buffer)
} catch (e: EOFException) {
break
}
digest.update(buffer[0])
}
return digest.digest()
}
private inline fun InputStream.readInSequence(block: (Int) -> Unit) {
var read: Int
while (this.read().also { read = it } != -1) {
block(read)
}
}
/**
* CRC32 算法
*/
actual fun crc32(key: ByteArray): Int = CRC32().apply { update(key) }.value.toInt()
/**
* hostname 解析 ipv4
*/
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
/**
* 让用户处理验证码
*
* @return 用户输入得到的验证码. 非 null 时一定 `length==4`.
*/
internal actual suspend fun solveCaptcha(captchaBuffer: IoBuffer): String? {
TODO("Unsupported yet")
}
\ No newline at end of file
package net.mamoe.mirai.utils.io
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.nio.read
import java.net.InetSocketAddress
import java.nio.channels.DatagramChannel
import java.nio.channels.ReadableByteChannel
/**
* 多平台适配的 DatagramChannel.
*/
actual class PlatformDatagramChannel actual constructor(serverHost: String, serverPort: Short) : Closeable {
private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt())
private val channel: DatagramChannel = DatagramChannel.open().connect(serverAddress)
@Throws(ReadPacketInternalException::class)
actual suspend fun read(buffer: IoBuffer) = withContext(Dispatchers.IO) {
try {
(channel as ReadableByteChannel).read(buffer)
} catch (e: Throwable) {
throw ReadPacketInternalException(e)
}
}
@Throws(SendPacketInternalException::class)
actual suspend fun send(buffer: IoBuffer) = withContext(Dispatchers.IO) {
buffer.readDirect {
try {
channel.send(it, serverAddress)
} catch (e: Throwable) {
throw SendPacketInternalException(e)
}
}
}
override fun close() {
channel.close()
}
actual val isOpen: Boolean get() = channel.isOpen
}
actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.mamoe.mirai">
<application
android:allowBackup="true"
android:supportsRtl="true">
</application>
</manifest>
\ No newline at end of file
...@@ -11,6 +11,7 @@ import net.mamoe.mirai.message.singleChain ...@@ -11,6 +11,7 @@ import net.mamoe.mirai.message.singleChain
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsPacket import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.RequestProfileDetailsResponse
import net.mamoe.mirai.qqAccount import net.mamoe.mirai.qqAccount
import net.mamoe.mirai.utils.SuspendLazy import net.mamoe.mirai.utils.SuspendLazy
import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail import net.mamoe.mirai.utils.internal.coerceAtLeastOrFail
...@@ -129,7 +130,7 @@ open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) { ...@@ -129,7 +130,7 @@ open class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
*/ */
suspend fun updateProfile(): Profile = bot.withSession { suspend fun updateProfile(): Profile = bot.withSession {
RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey) RequestProfileDetailsPacket(bot.qqAccount, id, sessionKey)
.sendAndExpect<RequestProfileDetailsPacket.Response, Profile> { it.profile } .sendAndExpect<RequestProfileDetailsResponse, Profile> { it.profile }
.await().let { .await().let {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
if ((::profile as SuspendLazy<Profile>).isInitialized()) { if ((::profile as SuspendLazy<Profile>).isInitialized()) {
......
...@@ -34,4 +34,9 @@ class FriendConversationInitializedEvent(bot: Bot, sender: QQ) : FriendEvent(bot ...@@ -34,4 +34,9 @@ class FriendConversationInitializedEvent(bot: Bot, sender: QQ) : FriendEvent(bot
/** /**
* 好友在线状态改变事件 * 好友在线状态改变事件
*/ */
class FriendOnlineStatusChangedEvent(bot: Bot, sender: QQ, val newStatus: OnlineStatus) : FriendEvent(bot, sender) class FriendOnlineStatusChangedEvent(bot: Bot, sender: QQ, val newStatus: OnlineStatus) : FriendEvent(bot, sender)
\ No newline at end of file
/**
* 机器人账号被 [sender] 删除好友
*/
class DeletedByFriendEvent(bot: Bot, qq: QQ) : FriendEvent(bot, qq)
\ No newline at end of file
...@@ -4,7 +4,6 @@ import net.mamoe.mirai.Bot ...@@ -4,7 +4,6 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.Cancellable import net.mamoe.mirai.event.Cancellable
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.Packet import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
/* Abstract */ /* Abstract */
...@@ -43,10 +42,10 @@ class BeforePacketSendEvent(bot: Bot, packet: OutgoingPacket) : OutgoingPacketEv ...@@ -43,10 +42,10 @@ class BeforePacketSendEvent(bot: Bot, packet: OutgoingPacket) : OutgoingPacketEv
/** /**
* 来自服务器的数据包的相关事件 * 来自服务器的数据包的相关事件
*/ */
sealed class ServerPacketEvent<P : ServerPacket>(bot: Bot, packet: P) : PacketEvent<P>(bot, packet) sealed class ServerPacketEvent<P : Packet>(bot: Bot, packet: P) : PacketEvent<P>(bot, packet)
/** /**
* 服务器数据包接收事件. 此时包已经解密完成. * 服务器数据包接收事件. 此时包已经解密完成.
*/ */
class ServerPacketReceivedEvent<P : ServerPacket>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet), class ServerPacketReceivedEvent<P : Packet>(bot: Bot, packet: P) : ServerPacketEvent<P>(bot, packet),
Cancellable Cancellable
\ No newline at end of file
...@@ -4,14 +4,13 @@ import kotlinx.coroutines.CancellationException ...@@ -4,14 +4,13 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelChildren import kotlinx.coroutines.cancelChildren
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter 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.TIMBotNetworkHandler.LoginHandler
import net.mamoe.mirai.network.protocol.tim.handler.* import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.HeartbeatPacket import net.mamoe.mirai.network.protocol.tim.packet.HeartbeatPacket
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.Packet 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.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.RequestSKeyPacket import net.mamoe.mirai.network.protocol.tim.packet.login.RequestSKeyPacket
import net.mamoe.mirai.utils.BotNetworkConfiguration import net.mamoe.mirai.utils.BotNetworkConfiguration
...@@ -48,6 +47,7 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel ...@@ -48,6 +47,7 @@ import net.mamoe.mirai.utils.io.PlatformDatagramChannel
@Suppress("PropertyName") @Suppress("PropertyName")
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope { interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
val socket: Socket val socket: Socket
val bot: Bot
/** /**
* 得到 [PacketHandler]. * 得到 [PacketHandler].
......
...@@ -11,7 +11,8 @@ import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler ...@@ -11,7 +11,8 @@ 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.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.SessionKey
import net.mamoe.mirai.utils.getGTK import net.mamoe.mirai.utils.getGTK
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
...@@ -21,7 +22,7 @@ import kotlin.coroutines.coroutineContext ...@@ -21,7 +22,7 @@ import kotlin.coroutines.coroutineContext
@Suppress("FunctionName", "NOTHING_TO_INLINE") @Suppress("FunctionName", "NOTHING_TO_INLINE")
internal inline fun TIMBotNetworkHandler.BotSession( internal inline fun TIMBotNetworkHandler.BotSession(
bot: Bot, bot: Bot,
sessionKey: ByteArray, sessionKey: SessionKey,
socket: DataPacketSocketAdapter socket: DataPacketSocketAdapter
) = BotSession(bot, sessionKey, socket, this) ) = BotSession(bot, sessionKey, socket, this)
...@@ -33,7 +34,7 @@ internal inline fun TIMBotNetworkHandler.BotSession( ...@@ -33,7 +34,7 @@ internal inline fun TIMBotNetworkHandler.BotSession(
*/ */
class BotSession( class BotSession(
val bot: Bot, val bot: Bot,
val sessionKey: ByteArray, val sessionKey: SessionKey,
val socket: DataPacketSocketAdapter, val socket: DataPacketSocketAdapter,
val NetworkScope: CoroutineScope val NetworkScope: CoroutineScope
) { ) {
...@@ -79,7 +80,10 @@ class BotSession( ...@@ -79,7 +80,10 @@ class BotSession(
* *
* @see Bot.withSession 转换接收器 (receiver, 即 `this` 的指向) 为 [BotSession] * @see Bot.withSession 转换接收器 (receiver, 即 `this` 的指向) 为 [BotSession]
*/ */
suspend inline fun <reified P : ServerPacket, R> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true, noinline handler: suspend (P) -> R): CompletableDeferred<R> { suspend inline fun <reified P : Packet, R> OutgoingPacket.sendAndExpect(
checkSequence: Boolean = true,
noinline handler: suspend (P) -> R
): CompletableDeferred<R> {
val deferred: CompletableDeferred<R> = CompletableDeferred(coroutineContext[Job]) val deferred: CompletableDeferred<R> = CompletableDeferred(coroutineContext[Job])
bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSession, checkSequence, coroutineContext + deferred).also { bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSession, checkSequence, coroutineContext + deferred).also {
it.toSend(this) it.toSend(this)
...@@ -92,13 +96,13 @@ class BotSession( ...@@ -92,13 +96,13 @@ class BotSession(
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P]. * 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P].
* 您将能从本函数的返回值 [CompletableDeferred] 接收到所期待的 [P] * 您将能从本函数的返回值 [CompletableDeferred] 接收到所期待的 [P]
*/ */
suspend inline fun <reified P : ServerPacket> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true): CompletableDeferred<P> = sendAndExpect<P, P>(checkSequence) { it } suspend inline fun <reified P : Packet> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true): CompletableDeferred<P> =
sendAndExpect<P, P>(checkSequence) { it }
suspend inline fun OutgoingPacket.send() = socket.sendPacket(this) suspend inline fun OutgoingPacket.send() = socket.sendPacket(this)
} }
suspend inline fun BotSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet)
inline val BotSession.isOpen: Boolean get() = socket.isOpen inline val BotSession.isOpen: Boolean get() = socket.isOpen
inline val BotSession.qqAccount: UInt get() = bot.account.id inline val BotSession.qqAccount: UInt get() = bot.account.id
...@@ -106,22 +110,22 @@ inline val BotSession.qqAccount: UInt get() = bot.account.id ...@@ -106,22 +110,22 @@ inline val BotSession.qqAccount: UInt get() = bot.account.id
* 取得 [BotNetworkHandler] 的 [BotSession]. * 取得 [BotNetworkHandler] 的 [BotSession].
* 实际上是一个捷径. * 实际上是一个捷径.
*/ */
val BotNetworkHandler<*>.session get() = this[ActionPacketHandler].session val BotNetworkHandler<*>.session: BotSession get() = this[ActionPacketHandler].session
/** /**
* 取得 [BotNetworkHandler] 的 sessionKey. * 取得 [BotNetworkHandler] 的 sessionKey.
* 实际上是一个捷径. * 实际上是一个捷径.
*/ */
inline val BotNetworkHandler<*>.sessionKey get() = this.session.sessionKey inline val BotNetworkHandler<*>.sessionKey: SessionKey get() = this.session.sessionKey
/** /**
* 取得 [Bot] 的 [BotSession]. * 取得 [Bot] 的 [BotSession].
* 实际上是一个捷径. * 实际上是一个捷径.
*/ */
inline val Bot.session get() = this.network.session inline val Bot.session: BotSession get() = this.network.session
/** /**
* 取得 [Bot] 的 `sessionKey`. * 取得 [Bot] 的 `sessionKey`.
* 实际上是一个捷径. * 实际上是一个捷径.
*/ */
inline val Bot.sessionKey get() = this.session.sessionKey inline val Bot.sessionKey: SessionKey get() = this.session.sessionKey
\ No newline at end of file \ No newline at end of file
...@@ -8,11 +8,10 @@ import kotlinx.coroutines.launch ...@@ -8,11 +8,10 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.isOpen import net.mamoe.mirai.network.isOpen
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.RequestAccountInfoPacket import net.mamoe.mirai.network.protocol.tim.packet.RequestAccountInfoPacket
import net.mamoe.mirai.network.protocol.tim.packet.ResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.RequestSKeyPacket import net.mamoe.mirai.network.protocol.tim.packet.login.RequestSKeyPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.SKey
import net.mamoe.mirai.network.qqAccount import net.mamoe.mirai.network.qqAccount
/** /**
...@@ -29,10 +28,10 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -29,10 +28,10 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
@ExperimentalStdlibApi @ExperimentalStdlibApi
override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) { override suspend fun onPacketReceived(packet: Packet): Unit = with(session) {
when (packet) { when (packet) {
is RequestSKeyPacket.Response -> { is SKey -> {
sKey = packet.sKey sKey = packet.delegate
cookies = "uin=o$qqAccount;skey=$sKey;" cookies = "uin=o$qqAccount;skey=$sKey;"
...@@ -50,10 +49,6 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) { ...@@ -50,10 +49,6 @@ class ActionPacketHandler(session: BotSession) : PacketHandler(session) {
} }
} }
is ServerEventPacket.Raw.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey))
is ServerEventPacket.Raw -> socket.distributePacket(packet.distribute())
is ResponsePacket.Encrypted<*> -> socket.distributePacket(packet.decrypt(sessionKey))
else -> { else -> {
} }
} }
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.handler package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.io.core.Closeable import kotlinx.io.core.Closeable
...@@ -6,7 +8,6 @@ import net.mamoe.mirai.event.events.ServerPacketReceivedEvent ...@@ -6,7 +8,6 @@ import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.io.PlatformDatagramChannel import net.mamoe.mirai.utils.io.PlatformDatagramChannel
/** /**
...@@ -35,11 +36,6 @@ interface DataPacketSocketAdapter : Closeable { ...@@ -35,11 +36,6 @@ interface DataPacketSocketAdapter : Closeable {
*/ */
val isOpen: Boolean val isOpen: Boolean
/**
* 分发数据包给 [PacketHandler]
*/
suspend fun distributePacket(packet: ServerPacket)
/** /**
* 发送一个数据包(非异步). * 发送一个数据包(非异步).
* *
......
...@@ -10,13 +10,12 @@ import net.mamoe.mirai.getGroup ...@@ -10,13 +10,12 @@ import net.mamoe.mirai.getGroup
import net.mamoe.mirai.getQQ import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.distributePacket import net.mamoe.mirai.network.protocol.tim.packet.EventPacket
import net.mamoe.mirai.network.protocol.tim.packet.FriendOnlineStatusChangedPacket import net.mamoe.mirai.network.protocol.tim.packet.FriendStatusChanged
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket import net.mamoe.mirai.network.protocol.tim.packet.action.SendFriendMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket import net.mamoe.mirai.network.protocol.tim.packet.action.SendGroupMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.event.*
import net.mamoe.mirai.network.qqAccount import net.mamoe.mirai.network.qqAccount
/** /**
...@@ -28,35 +27,29 @@ import net.mamoe.mirai.network.qqAccount ...@@ -28,35 +27,29 @@ import net.mamoe.mirai.network.qqAccount
class EventPacketHandler(session: BotSession) : PacketHandler(session) { class EventPacketHandler(session: BotSession) : PacketHandler(session) {
companion object Key : PacketHandler.Key<EventPacketHandler> companion object Key : PacketHandler.Key<EventPacketHandler>
override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) { override suspend fun onPacketReceived(packet: Packet): Unit = with(session) {
when (packet) { when (packet) {
is ServerGroupUploadFileEventPacket -> { is EventPacket.FriendMessage -> {
//todo if (!packet.isPrevious) FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message) else null
} }
is FriendMessageEventPacket -> { is EventPacket.GroupMessage -> {
if (!packet.isPrevious) FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message).broadcast()
}
is GroupMessageEventPacket -> {
if (packet.qq == bot.account.id) return if (packet.qq == bot.account.id) return
GroupMessageEvent( GroupMessageEvent(
bot, bot.getGroup(GroupId(packet.groupNumber)), bot.getQQ(packet.qq), packet.message, packet.senderPermission, packet.senderName bot, bot.getGroup(GroupId(packet.groupNumber)), bot.getQQ(packet.qq), packet.message, packet.senderPermission, packet.senderName
).broadcast() )
} }
is FriendConversationInitializedEventPacket -> FriendConversationInitializedEvent(bot, bot.getQQ(packet.qq)).broadcast() is EventPacket.FriendConversationInitialize -> FriendConversationInitializedEvent(bot, bot.getQQ(packet.qq))
is FriendOnlineStatusChangedPacket -> FriendOnlineStatusChangedEvent(bot, bot.getQQ(packet.qq), packet.status).broadcast() is FriendStatusChanged -> FriendOnlineStatusChangedEvent(bot, bot.getQQ(packet.qq), packet.status)
is FriendImageIdRequestPacket.Response -> packet.imageId?.let { FriendImageIdObtainedEvent(bot, it) } is FriendImageIdRequestPacket.Response -> packet.imageId?.let { FriendImageIdObtainedEvent(bot, it) }
is GroupMemberPermissionChangedEventPacket -> MemberPermissionChangedEvent( is EventPacket.MemberPermissionChange ->
bot, bot.getGroup(packet.groupId.groupId()), bot.getQQ(packet.qq), packet.kind MemberPermissionChangedEvent(bot, bot.getGroup(packet.groupId.groupId()), bot.getQQ(packet.qq), packet.kind)
).broadcast()
is FriendOnlineStatusChangedPacket.Encrypted -> distributePacket(packet.decrypt(sessionKey)) else -> null
} }?.broadcast()
} }
suspend fun sendFriendMessage(qq: QQ, message: MessageChain) { suspend fun sendFriendMessage(qq: QQ, message: MessageChain) {
......
package net.mamoe.mirai.network.protocol.tim.handler package net.mamoe.mirai.network.protocol.tim.handler
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.Packet
/** /**
* 数据包(接受/发送)处理器 * 数据包(接受/发送)处理器
...@@ -9,7 +9,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket ...@@ -9,7 +9,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
abstract class PacketHandler( abstract class PacketHandler(
val session: BotSession val session: BotSession
) { ) {
abstract suspend fun onPacketReceived(packet: ServerPacket) abstract suspend fun onPacketReceived(packet: Packet)
interface Key<T : PacketHandler> interface Key<T : PacketHandler>
......
...@@ -6,7 +6,7 @@ import kotlinx.coroutines.CompletableDeferred ...@@ -6,7 +6,7 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.Packet
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.reflect.KClass import kotlin.reflect.KClass
...@@ -23,7 +23,7 @@ import kotlin.reflect.KClass ...@@ -23,7 +23,7 @@ import kotlin.reflect.KClass
* *
* @see BotSession.sendAndExpect * @see BotSession.sendAndExpect
*/ */
class TemporaryPacketHandler<P : ServerPacket, R>( class TemporaryPacketHandler<P : Packet, R>(
private val expectationClass: KClass<P>, private val expectationClass: KClass<P>,
private val deferred: CompletableDeferred<R>, private val deferred: CompletableDeferred<R>,
private val fromSession: BotSession, private val fromSession: BotSession,
...@@ -54,10 +54,10 @@ class TemporaryPacketHandler<P : ServerPacket, R>( ...@@ -54,10 +54,10 @@ class TemporaryPacketHandler<P : ServerPacket, R>(
session.socket.sendPacket(toSend) session.socket.sendPacket(toSend)
} }
internal fun filter(session: BotSession, packet: ServerPacket): Boolean = internal fun filter(session: BotSession, packet: Packet, sequenceId: UShort): Boolean =
expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) packet.sequenceId == toSend.sequenceId else true expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) sequenceId == toSend.sequenceId else true
internal suspend fun doReceiveWithoutExceptions(packet: ServerPacket) { internal suspend fun doReceiveWithoutExceptions(packet: Packet) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val ret = try { val ret = try {
withContext(callerContext) { withContext(callerContext) {
......
...@@ -7,7 +7,7 @@ import kotlin.reflect.KClass ...@@ -7,7 +7,7 @@ import kotlin.reflect.KClass
/** /**
* 包 ID. 除特殊外, [OutgoingPacketBuilder] 都需要这个注解来指定包 ID. * 包 ID. 除特殊外, [PacketFactory] 都需要这个注解来指定包 ID.
*/ */
@MustBeDocumented @MustBeDocumented
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
...@@ -21,7 +21,7 @@ inline val AnnotatedId.value: UShort get() = id.value ...@@ -21,7 +21,7 @@ inline val AnnotatedId.value: UShort get() = id.value
/** /**
* 标记这个包对应的事件. * 标记这个包对应的事件.
* 这个注解应该被标记在 [ServerPacket] 上 * 这个注解应该被标记在 [Packet] 上
*/ */
@MustBeDocumented @MustBeDocumented
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
...@@ -34,7 +34,7 @@ annotation class CorrespondingEvent( ...@@ -34,7 +34,7 @@ annotation class CorrespondingEvent(
* 版本信息 * 版本信息
*/ */
@MustBeDocumented @MustBeDocumented
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.BINARY) @Retention(AnnotationRetention.BINARY)
internal annotation class PacketVersion(val date: String, val timVersion: String) internal annotation class PacketVersion(val date: String, val timVersion: String)
......
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
/**
* 会话密匙
*/
inline class SessionKey(override val value: ByteArray) : DecrypterByteArray {
companion object Type : DecrypterType<SessionKey>
}
/**
* [ByteArray] 解密器
*/
interface DecrypterByteArray : Decrypter {
val value: ByteArray
override fun decrypt(packet: ByteReadPacket): ByteReadPacket = packet.decryptBy(value)
}
/**
* [IoBuffer] 解密器
*/
interface DecrypterIoBuffer : Decrypter {
val value: IoBuffer
override fun decrypt(packet: ByteReadPacket): ByteReadPacket = packet.decryptBy(value)
}
/**
* 连接在一起的解密器
*/
inline class LinkedDecrypter(inline val block: (ByteReadPacket) -> ByteReadPacket) : Decrypter {
override fun decrypt(packet: ByteReadPacket): ByteReadPacket = block(packet)
}
object NoDecrypter : Decrypter, DecrypterType<NoDecrypter> {
override fun decrypt(packet: ByteReadPacket): ByteReadPacket = packet
}
/**
* 解密器
*/
interface Decrypter {
fun decrypt(packet: ByteReadPacket): ByteReadPacket
/**
* 连接后将会先用 this 解密, 再用 [another] 解密
*/
operator fun plus(another: Decrypter): Decrypter = LinkedDecrypter { another.decrypt(this.decrypt(it)) }
}
interface DecrypterType<D : Decrypter>
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "JoinDeclarationAndAssignment")
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
...@@ -6,30 +6,35 @@ import kotlinx.io.core.ByteReadPacket ...@@ -6,30 +6,35 @@ import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.event.events.FriendOnlineStatusChangedEvent
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.OnlineStatus import net.mamoe.mirai.utils.OnlineStatus
import kotlin.properties.Delegates
@CorrespondingEvent(FriendOnlineStatusChangedEvent::class)
abstract class FriendStatusChanged : Packet {
abstract val qq: UInt
abstract val status: OnlineStatus
}
/** /**
* 好友在线状态改变 * 好友在线状态改变
*/ */
@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE) @AnnotatedId(KnownPacketId.FRIEND_ONLINE_STATUS_CHANGE)
class FriendOnlineStatusChangedPacket(input: ByteReadPacket) : ServerPacket(input) { object FriendOnlineStatusChangedPacket : SessionPacketFactory<FriendStatusChanged>() {
var qq: UInt by Delegates.notNull()
lateinit var status: OnlineStatus override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): FriendStatusChanged =
object : FriendStatusChanged() {
override val qq: UInt
override val status: OnlineStatus
override fun decode() = with(input) { init {
qq = readUInt() qq = readUInt()
discardExact(8) discardExact(8)
val id = readUByte() val id = readUByte()
status = OnlineStatus.ofId(id) ?: error("Unknown online status id $id") status = OnlineStatus.ofId(id) ?: error("Unknown online status id $id")
}
} }
//在线 XX XX XX XX 01 00 00 00 00 00 00 00 0A 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00 //在线 XX XX XX XX 01 00 00 00 00 00 00 00 0A 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00
//忙碌 XX XX XX XX 01 00 00 00 00 00 00 00 32 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00 //忙碌 XX XX XX XX 01 00 00 00 00 00 00 00 32 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00
@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): FriendOnlineStatusChangedPacket =
FriendOnlineStatusChangedPacket(this.decryptBy(sessionKey)).applySequence(sequenceId)
}
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet ...@@ -4,6 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeUByte import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeHex
...@@ -15,11 +16,11 @@ import net.mamoe.mirai.utils.io.writeQQ ...@@ -15,11 +16,11 @@ import net.mamoe.mirai.utils.io.writeQQ
* @author Him188moe * @author Him188moe
*/ */
@AnnotatedId(KnownPacketId.ACCOUNT_INFO) @AnnotatedId(KnownPacketId.ACCOUNT_INFO)
object RequestAccountInfoPacket : OutgoingPacketBuilder { object RequestAccountInfoPacket : SessionPacketFactory<RequestAccountInfoPacket.Response>() {
operator fun invoke( operator fun invoke(
qq: UInt, qq: UInt,
sessionKey: ByteArray sessionKey: SessionKey
) = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(qq) writeQQ(qq)
writeHex(TIMProtocol.fixVer2) writeHex(TIMProtocol.fixVer2)
encryptAndWrite(sessionKey) { encryptAndWrite(sessionKey) {
...@@ -29,10 +30,11 @@ object RequestAccountInfoPacket : OutgoingPacketBuilder { ...@@ -29,10 +30,11 @@ object RequestAccountInfoPacket : OutgoingPacketBuilder {
} }
} }
@AnnotatedId(KnownPacketId.ACCOUNT_INFO) object Response : Packet {
class Response(input: ByteReadPacket) : ResponsePacket(input) {
//等级 //等级
//升级剩余活跃天数 //升级剩余活跃天数
//ignored //ignored
} }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response
} }
\ No newline at end of file
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.writeHex
...@@ -10,10 +11,10 @@ import net.mamoe.mirai.utils.io.writeQQ ...@@ -10,10 +11,10 @@ import net.mamoe.mirai.utils.io.writeQQ
@NoLog @NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT) @AnnotatedId(KnownPacketId.HEARTBEAT)
object HeartbeatPacket : OutgoingPacketBuilder { object HeartbeatPacket : SessionPacketFactory<HeartbeatPacketResponse>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
sessionKey: ByteArray sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer) writeHex(TIMProtocol.fixVer)
...@@ -22,7 +23,10 @@ object HeartbeatPacket : OutgoingPacketBuilder { ...@@ -22,7 +23,10 @@ object HeartbeatPacket : OutgoingPacketBuilder {
} }
} }
@NoLog override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): HeartbeatPacketResponse =
@AnnotatedId(KnownPacketId.HEARTBEAT) HeartbeatPacketResponse
class Response(input: ByteReadPacket) : ResponsePacket(input) }
}
\ No newline at end of file @NoLog
@AnnotatedId(KnownPacketId.HEARTBEAT)
object HeartbeatPacketResponse : Packet
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "MemberVisibilityCanBePrivate")
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.atomicfu.atomic
import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.use import kotlinx.io.core.use
...@@ -18,8 +17,8 @@ import kotlin.jvm.JvmOverloads ...@@ -18,8 +17,8 @@ import kotlin.jvm.JvmOverloads
*/ */
class OutgoingPacket( class OutgoingPacket(
name: String?, name: String?,
override val packetId: PacketId, val packetId: PacketId,
override val sequenceId: UShort, val sequenceId: UShort,
internal val delegate: ByteReadPacket internal val delegate: ByteReadPacket
) : Packet { ) : Packet {
private val name: String by lazy { private val name: String by lazy {
...@@ -31,28 +30,15 @@ class OutgoingPacket( ...@@ -31,28 +30,15 @@ class OutgoingPacket(
constructor(annotation: AnnotatedId, sequenceId: UShort, delegate: ByteReadPacket) : constructor(annotation: AnnotatedId, sequenceId: UShort, delegate: ByteReadPacket) :
this(annotation.toString(), annotation.id, sequenceId, delegate) this(annotation.toString(), annotation.id, sequenceId, delegate)
override fun toString(): String = packetToString(name) override fun toString(): String = packetToString(packetId.value, sequenceId, name)
} }
/** /**
* 发给服务器的数据包的构建器. * 登录完成建立 session 之后发出的包.
* 应由一个 `object` 实现, 且实现 `operator fun invoke` * 均使用 sessionKey 加密
*/ */
interface OutgoingPacketBuilder { abstract class SessionPacketFactory<out TPacket : Packet> : PacketFactory<TPacket, SessionKey>(SessionKey) {
/** final override fun decrypt(input: ByteReadPacket, decrypter: SessionKey): ByteReadPacket = decrypter.decrypt(input)
* 2 Ubyte.
* 默认为读取注解 [AnnotatedId]
*/
val annotatedId: AnnotatedId
get() = (this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)
?: error("Annotation AnnotatedId not found")
companion object {
private val sequenceIdInternal = atomic(1)
@PublishedApi
internal fun atomicNextSequenceId(): UShort = sequenceIdInternal.getAndIncrement().toUShort()
}
} }
/** /**
...@@ -61,10 +47,10 @@ interface OutgoingPacketBuilder { ...@@ -61,10 +47,10 @@ interface OutgoingPacketBuilder {
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id. * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/ */
@JvmOverloads @JvmOverloads
fun OutgoingPacketBuilder.buildOutgoingPacket( fun PacketFactory<*, *>.buildOutgoingPacket(
name: String? = null, name: String? = null,
id: PacketId = this.annotatedId.id, id: PacketId = this.id,
sequenceId: UShort = OutgoingPacketBuilder.atomicNextSequenceId(), sequenceId: UShort = PacketFactory.atomicNextSequenceId(),
headerSizeHint: Int = 0, headerSizeHint: Int = 0,
block: BytePacketBuilder.() -> Unit block: BytePacketBuilder.() -> Unit
): OutgoingPacket { ): OutgoingPacket {
...@@ -88,12 +74,12 @@ fun OutgoingPacketBuilder.buildOutgoingPacket( ...@@ -88,12 +74,12 @@ fun OutgoingPacketBuilder.buildOutgoingPacket(
* 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id. * 若不提供参数 [id], 则会通过注解 [AnnotatedId] 获取 id.
*/ */
@JvmOverloads @JvmOverloads
fun OutgoingPacketBuilder.buildSessionPacket( fun PacketFactory<*, *>.buildSessionPacket(
bot: UInt, bot: UInt,
sessionKey: ByteArray, sessionKey: SessionKey,
name: String? = null, name: String? = null,
id: PacketId = this.annotatedId.id, id: PacketId = this.id,
sequenceId: UShort = OutgoingPacketBuilder.atomicNextSequenceId(), sequenceId: UShort = PacketFactory.atomicNextSequenceId(),
headerSizeHint: Int = 0, headerSizeHint: Int = 0,
block: BytePacketBuilder.() -> Unit block: BytePacketBuilder.() -> Unit
): OutgoingPacket = buildOutgoingPacket(name, id, sequenceId, headerSizeHint) { ): OutgoingPacket = buildOutgoingPacket(name, id, sequenceId, headerSizeHint) {
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeQQ
object OutgoingRawPacket : OutgoingPacketBuilder {
operator fun invoke(
id: PacketId,
bot: UInt,
version: ByteArray,
sessionKey: ByteArray,
data: ByteArray
): OutgoingPacket = buildOutgoingPacket(id = id) {
writeQQ(bot)
writeFully(version)
encryptAndWrite(sessionKey) {
writeFully(data)
}
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
import kotlin.reflect.KClass
/**
* 标记一个 [OutgoingPacket] 的服务器回复包.
* 在这个包发送时将会记录回复包信息.
* 收到回复包时将解密为指定的包
*
*
* // TODO: 2019/10/27 暂未实现. 计划中
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
annotation class Response(
val responseClass: KClass<out ResponsePacket>
)
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
/**
* 返回包.
* 在登录完成后, 任意一个 [OutgoingPacket] 发送后服务器都会给返回包. 则一个 [OutgoingPacket] 一定对应一个 [ResponsePacket]
* 它们都使用 sessionKey 解密.
* 它们都必须有一个公开的仅有一个 [ByteReadPacket] 参数的构造器.
*
* 注意: 需要指定 ID, 通过 [AnnotatedId].
*/
abstract class ResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
/**
* 加密过的 [ResponsePacket]. 将会在处理时解密为对应的 [ResponsePacket]
*/
class Encrypted<P : ResponsePacket>(input: ByteReadPacket, val constructor: (ByteReadPacket) -> P) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): P = constructor(decryptBy(sessionKey)).applySequence(sequenceId)
}
companion object {
@Suppress("FunctionName")
inline fun <reified P : ResponsePacket> Encrypted(input: ByteReadPacket): Encrypted<P> = Encrypted(input) { P::class.constructors.first().call(it) }
}
}
...@@ -3,78 +3,30 @@ ...@@ -3,78 +3,30 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.decryptBy import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.parseServerPacket
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.properties.Delegates
/** fun ByteReadPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
* 来自服务器的数据包 fun ByteReadPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
* fun ByteReadPacket.decryptBy(keyHex: String): ByteReadPacket = decryptBy(keyHex.hexToBytes())
* @see parseServerPacket 解析包种类
*/
abstract class ServerPacket(val input: ByteReadPacket) : Packet, Closeable {
override val packetId: PacketId by lazy {
(this::class.annotations.firstOrNull { it is AnnotatedId } as? AnnotatedId)?.id
?: error("Annotation AnnotatedId not found")
}
override var sequenceId: UShort by Delegates.notNull() inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
open fun decode() {
}
override fun close() = this.input.close()
override fun toString(): String = this.packetToString()
fun <S : ServerPacket> S.applySequence() = this.applySequence(this@ServerPacket.sequenceId)
}
fun <S : ServerPacket> S.applySequence(sequenceId: UShort): S {
this.sequenceId = sequenceId
return this
}
fun ServerPacket.decryptBy(key: ByteArray): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
fun ServerPacket.decryptBy(key: IoBuffer): ByteReadPacket = decryptAsByteArray(key) { data -> ByteReadPacket(data, 0) }
fun ServerPacket.decryptBy(keyHex: String): ByteReadPacket = this.decryptBy(keyHex.hexToBytes())
fun ServerPacket.decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket =
this.decryptAsByteArray(key1) { data ->
data.decryptBy(key2).toReadPacket()
}
fun ServerPacket.decryptBy(key1: String, key2: ByteArray): ByteReadPacket = this.decryptBy(key1.hexToBytes(), key2)
fun ServerPacket.decryptBy(key1: String, key2: IoBuffer): ByteReadPacket =
this.decryptBy(key1.hexToBytes(), key2.readBytes())
fun ServerPacket.decryptBy(key1: ByteArray, key2: String): ByteReadPacket = this.decryptBy(key1, key2.hexToBytes())
fun ServerPacket.decryptBy(keyHex1: String, keyHex2: String): ByteReadPacket =
this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
inline fun <R> ServerPacket.decryptAsByteArray(key: ByteArray, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
val length = input.remaining.toInt() - 1 val length = remaining.toInt()
input.readFully(it, 0, length) readFully(it, 0, length)
consumer(it.decryptBy(key, length)) consumer(it.decryptBy(key, length))
}.also { input.close() } }.also { close() }
inline fun <R> ServerPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R = inline fun <R> ByteReadPacket.decryptAsByteArray(keyHex: String, consumer: (ByteArray) -> R): R =
this.decryptAsByteArray(keyHex.hexToBytes(), consumer) this.decryptAsByteArray(keyHex.hexToBytes(), consumer)
inline fun <R> ServerPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R = inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, consumer: (ByteArray) -> R): R =
ByteArrayPool.useInstance { ByteArrayPool.useInstance {
val length = input.remaining.toInt() - 1 val length = remaining.toInt()
input.readFully(it, 0, length) readFully(it, 0, length)
consumer(it.decryptBy(key, length)) consumer(it.decryptBy(key, length))
}.also { input.close() } }.also { close() }
...@@ -2,31 +2,6 @@ ...@@ -2,31 +2,6 @@
package net.mamoe.mirai.network.protocol.tim.packet package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.toUHexString
class UnknownServerPacket(
input: ByteReadPacket,
override val packetId: PacketId,
override var sequenceId: UShort
) : ServerPacket(input) {
override fun decode() {
val raw = this.input.readBytes()
MiraiLogger.debug("UnknownServerPacket data: " + raw.toUHexString())
}
class Encrypted(
input: ByteReadPacket,
override val packetId: PacketId,
override var sequenceId: UShort
) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): UnknownServerPacket =
UnknownServerPacket(this.decryptBy(sessionKey), this.packetId, this.sequenceId)
}
}
/* /*
ID: 00 17 ID: 00 17
......
...@@ -4,66 +4,108 @@ package net.mamoe.mirai.network.protocol.tim.packet.action ...@@ -4,66 +4,108 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import kotlinx.io.core.readUShort import kotlinx.io.core.readUShort
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.network.protocol.tim.packet.action.CanAddFriendResponse.State
import net.mamoe.mirai.utils.io.writeHex import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.io.writeQQ import kotlin.properties.Delegates
// 01BC 曾用名查询. 查到的是这个人的
// 发送 00 00
// 3E 03 3F A2 //bot
// 59 17 3E 05 //目标
//
// 接受: 00 00 00 03
// [00 00 00 0C] E6 A5 BC E4 B8 8A E5 B0 8F E7 99 BD
// [00 00 00 10] 68 69 6D 31 38 38 E7 9A 84 E5 B0 8F 64 69 63 6B
// [00 00 00 0F] E4 B8 B6 E6 9A 97 E8 A3 94 E5 89 91 E9 AD 94
/** /**
* 向服务器检查是否可添加某人为好友 * 查询某人与机器人账号有关的曾用名 (备注).
* *
* @author Him188moe * 曾用名可能是:
* - 昵称
* - 共同群内的群名片
*/ */
@Response(CanAddFriendPacket.Response::class) @PacketVersion(date = "2019.11.02", timVersion = "2.3.2.21173")
@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND) object QueryPreviousNamePacket : SessionPacketFactory<QueryPreviousNameResponse>() {
object CanAddFriendPacket : OutgoingPacketBuilder {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
qq: UInt, sessionKey: SessionKey,
sessionKey: ByteArray target: UInt
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeZero(2)
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer2) writeQQ(target)
encryptAndWrite(sessionKey) {
writeQQ(qq)
}
} }
@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND) override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): QueryPreviousNameResponse =
class Response(input: ByteReadPacket) : ResponsePacket(input) { QueryPreviousNameResponse().apply {
lateinit var state: State names = Array(readUInt().toInt()) {
discardExact(2)
enum class State { readUShortLVString()
/** }
* 已经添加
*/
ALREADY_ADDED,
/**
* 需要验证信息
*/
REQUIRE_VERIFICATION,
/**
* 不需要验证信息
*/
NOT_REQUIRE_VERIFICATION,
/**
* 对方拒绝添加
*/
FAILED,
} }
}
class QueryPreviousNameResponse : Packet {
lateinit var names: Array<String>
}
// 需要验证消息
// 0065 发送 03 07 57 37 E8
// 0065 接受 03 07 57 37 E8 10 40 00 00 10 14 20 00 00 00 00 00 00 00 01 00 00 00 00 00
/**
* 添加好友结果
*
* @author Him188moe
*/
enum class AddFriendResult {
/**
* 等待对方处理
*/
WAITING_FOR_AGREEMENT,
/**
* 和对方已经是好友了
*/
ALREADY_ADDED,
/**
* 对方设置为不添加好友等
*/
FAILED,
}
override fun decode() = with(input) { /**
//需要验证信息 00 23 24 8B 00 01 * 向服务器检查是否可添加某人为好友
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND)
object CanAddFriendPacket : SessionPacketFactory<CanAddFriendResponse>() {
operator fun invoke(
bot: UInt,
qq: UInt,
sessionKey: SessionKey
): OutgoingPacket = buildSessionPacket(bot, sessionKey) {
writeQQ(qq)
}
if (input.remaining > 20) {//todo check override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CanAddFriendResponse =
CanAddFriendResponse().apply {
if (remaining > 20) {//todo check
state = State.ALREADY_ADDED state = State.ALREADY_ADDED
return return@apply
} }
discardExact(4)//对方qq号 qq = readUInt()
state = when (val state = readUShort().toUInt()) { state = when (val state = readUShort().toUInt()) {
0x00u -> State.NOT_REQUIRE_VERIFICATION 0x00u -> State.NOT_REQUIRE_VERIFICATION
0x01u -> State.REQUIRE_VERIFICATION//需要验证信息 0x01u -> State.REQUIRE_VERIFICATION//需要验证信息
...@@ -74,18 +116,43 @@ object CanAddFriendPacket : OutgoingPacketBuilder { ...@@ -74,18 +116,43 @@ object CanAddFriendPacket : OutgoingPacketBuilder {
else -> throw IllegalStateException(state.toString()) else -> throw IllegalStateException(state.toString())
} }
} }
}
class CanAddFriendResponse : Packet {
var qq: UInt by Delegates.notNull()
lateinit var state: State
enum class State {
/**
* 已经添加
*/
ALREADY_ADDED,
/**
* 需要验证信息
*/
REQUIRE_VERIFICATION,
/**
* 不需要验证信息
*/
NOT_REQUIRE_VERIFICATION,
/**
* 对方拒绝添加
*/
FAILED,
} }
} }
/** /**
* 请求添加好友 * 请求添加好友
*/ */
@AnnotatedId(KnownPacketId.CAN_ADD_FRIEND) @AnnotatedId(KnownPacketId.CAN_ADD_FRIEND)
object AddFriendPacket : OutgoingPacketBuilder { object AddFriendPacket : SessionPacketFactory<AddFriendPacket.AddFriendResponse>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
qq: UInt, qq: UInt,
sessionKey: ByteArray sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer2) writeHex(TIMProtocol.fixVer2)
...@@ -94,28 +161,11 @@ object AddFriendPacket : OutgoingPacketBuilder { ...@@ -94,28 +161,11 @@ object AddFriendPacket : OutgoingPacketBuilder {
writeQQ(qq) writeQQ(qq)
} }
} }
}
/** class AddFriendResponse : Packet
* 添加好友/群的回复
*/
abstract class ServerAddContactResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
class Raw(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() {
}
fun distribute(): ServerAddContactResponsePacket {
TODO() override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): AddFriendResponse {
} TODO()
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(decryptBy(sessionKey))
}
} }
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.action
/**
* 添加好友结果
*
* @author Him188moe
*/
enum class AddFriendResult {
/**
* 等待对方处理
*/
WAITING_FOR_AGREEMENT,
/**
* 和对方已经是好友了
*/
ALREADY_ADDED,
/**
* 对方设置为不添加好友等
*/
FAILED,
}
\ No newline at end of file
...@@ -5,6 +5,7 @@ package net.mamoe.mirai.network.protocol.tim.packet.action ...@@ -5,6 +5,7 @@ package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toPacket import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
...@@ -12,62 +13,58 @@ import net.mamoe.mirai.utils.md5 ...@@ -12,62 +13,58 @@ import net.mamoe.mirai.utils.md5
@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE) @AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
object SendFriendMessagePacket : OutgoingPacketBuilder { object SendFriendMessagePacket : SessionPacketFactory<SendFriendMessagePacket.Response>() {
operator fun invoke( operator fun invoke(
botQQ: UInt, botQQ: UInt,
targetQQ: UInt, targetQQ: UInt,
sessionKey: ByteArray, sessionKey: SessionKey,
message: MessageChain message: MessageChain
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildSessionPacket(botQQ, sessionKey) {
writeQQ(botQQ) writeQQ(botQQ)
writeHex(TIMProtocol.version0x02) writeQQ(targetQQ)
writeHex("00 00 00 08 00 01 00 04 00 00 00 00")
encryptAndWrite(sessionKey) { writeHex("38 03")
writeQQ(botQQ) writeQQ(botQQ)
writeQQ(targetQQ) writeQQ(targetQQ)
writeHex("00 00 00 08 00 01 00 04 00 00 00 00") writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
writeHex("38 03") writeHex("00 0B")
writeQQ(botQQ) writeRandom(2)
writeQQ(targetQQ) writeTime()
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes())) writeHex(
writeHex("00 0B") "01 1D" +
writeRandom(2) " 00 00 00 00"
writeTime() )
writeHex(
"01 1D" +
" 00 00 00 00"
)
//消息过多要分包发送 //消息过多要分包发送
//如果只有一个 //如果只有一个
writeByte(0x01) writeByte(0x01)
writeByte(0)//第几个包 writeByte(0)//第几个包
writeUByte(0x00u) writeUByte(0x00u)
//如果大于一个, //如果大于一个,
//writeByte(0x02)//数量 //writeByte(0x02)//数量
//writeByte(0)//第几个包 //writeByte(0)//第几个包
//writeByte(0x91)//why? //writeByte(0x91)//why?
writeHex("00 01 4D 53 47 00 00 00 00 00") writeHex("00 01 4D 53 47 00 00 00 00 00")
writeTime() writeTime()
writeRandom(4) writeRandom(4)
writeHex("00 00 00 00 0C 00 86") writeHex("00 00 00 00 0C 00 86")
writeHex(TIMProtocol.messageConstNewest) writeHex(TIMProtocol.messageConstNewest)
writeZero(2) writeZero(2)
writePacket(message.toPacket(false)) writePacket(message.toPacket(false))
/* /*
//Plain text //Plain text
val bytes = event.toPacket() val bytes = event.toPacket()
it.writeByte(0x01) it.writeByte(0x01)
it.writeShort(bytes.size + 3) it.writeShort(bytes.size + 3)
it.writeByte(0x01) it.writeByte(0x01)
it.writeShort(bytes.size) it.writeShort(bytes.size)
it.write(bytes)*/ it.write(bytes)*/
}
} }
@AnnotatedId(KnownPacketId.SEND_FRIEND_MESSAGE) object Response : Packet
class Response(input: ByteReadPacket) : ResponsePacket(input)
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response
} }
\ No newline at end of file
...@@ -6,17 +6,16 @@ import kotlinx.io.core.ByteReadPacket ...@@ -6,17 +6,16 @@ import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.contact.GroupInternalId import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toPacket import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
object SendGroupMessagePacket : SessionPacketFactory<SendGroupMessagePacket.Response>() {
@AnnotatedId(KnownPacketId.SEND_GROUP_MESSAGE)
object SendGroupMessagePacket : OutgoingPacketBuilder {
operator fun invoke( operator fun invoke(
botQQ: UInt, botQQ: UInt,
groupInternalId: GroupInternalId, groupInternalId: GroupInternalId,
sessionKey: ByteArray, sessionKey: SessionKey,
message: MessageChain message: MessageChain
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(botQQ) writeQQ(botQQ)
...@@ -46,6 +45,7 @@ object SendGroupMessagePacket : OutgoingPacketBuilder { ...@@ -46,6 +45,7 @@ object SendGroupMessagePacket : OutgoingPacketBuilder {
} }
} }
@AnnotatedId(KnownPacketId.SEND_GROUP_MESSAGE) object Response : Packet
class Response(input: ByteReadPacket) : ResponsePacket(input)
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response
} }
\ No newline at end of file
...@@ -12,7 +12,7 @@ import kotlinx.io.core.* ...@@ -12,7 +12,7 @@ import kotlinx.io.core.*
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.events.FriendImageIdObtainedEvent import net.mamoe.mirai.event.events.FriendImageIdObtainedEvent
import net.mamoe.mirai.message.ImageId import net.mamoe.mirai.message.ImageId
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket.Response.State.* import net.mamoe.mirai.network.protocol.tim.packet.action.FriendImageIdRequestPacket.Response.State.*
import net.mamoe.mirai.network.qqAccount import net.mamoe.mirai.network.qqAccount
...@@ -126,18 +126,19 @@ internal suspend inline fun HttpClient.postImage( ...@@ -126,18 +126,19 @@ internal suspend inline fun HttpClient.postImage(
imageInput.close() imageInput.close()
} }
/*
/** /**
* 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00 * 似乎没有必要. 服务器的返回永远都是 01 00 00 00 02 00 00
*/ */
@Deprecated("Useless packet") @Deprecated("Useless packet")
@AnnotatedId(KnownPacketId.SUBMIT_IMAGE_FILE_NAME) @AnnotatedId(KnownPacketId.SUBMIT_IMAGE_FILE_NAME)
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
object SubmitImageFilenamePacket : OutgoingPacketBuilder { object SubmitImageFilenamePacket : PacketFactory {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
target: UInt, target: UInt,
filename: String, filename: String,
sessionKey: ByteArray sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer2)//? writeHex(TIMProtocol.fixVer2)//?
...@@ -166,9 +167,8 @@ object SubmitImageFilenamePacket : OutgoingPacketBuilder { ...@@ -166,9 +167,8 @@ object SubmitImageFilenamePacket : OutgoingPacketBuilder {
//解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1C 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 29 37 42 53 4B 48 32 44 35 54 51 28 5A 35 7D 35 24 56 5D 32 35 49 4E 2E 6A 70 67 00 00 03 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02 //解密body=01 3E 03 3F A2 7C BC D3 C1 00 00 27 1C 00 0A 00 01 00 01 00 30 55 73 65 72 44 61 74 61 43 75 73 74 6F 6D 46 61 63 65 3A 31 5C 29 37 42 53 4B 48 32 44 35 54 51 28 5A 35 7D 35 24 56 5D 32 35 49 4E 2E 6A 70 67 00 00 03 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 02 2F 02
} }
@AnnotatedId(KnownPacketId.SUBMIT_IMAGE_FILE_NAME)
@PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.19", timVersion = "2.3.2.21173")
class Response(input: ByteReadPacket) : ResponsePacket(input) { class Response {
override fun decode() = with(input) { override fun decode() = with(input) {
require(readBytes().contentEquals(expecting)) require(readBytes().contentEquals(expecting))
} }
...@@ -177,7 +177,7 @@ object SubmitImageFilenamePacket : OutgoingPacketBuilder { ...@@ -177,7 +177,7 @@ object SubmitImageFilenamePacket : OutgoingPacketBuilder {
private val expecting = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00) private val expecting = byteArrayOf(0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00)
} }
} }
} }*/
/** /**
...@@ -188,10 +188,10 @@ object SubmitImageFilenamePacket : OutgoingPacketBuilder { ...@@ -188,10 +188,10 @@ object SubmitImageFilenamePacket : OutgoingPacketBuilder {
*/ */
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID) @AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
object FriendImageIdRequestPacket : OutgoingPacketBuilder { object FriendImageIdRequestPacket : SessionPacketFactory<FriendImageIdRequestPacket.Response>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
sessionKey: ByteArray, sessionKey: SessionKey,
target: UInt, target: UInt,
image: ExternalImage image: ExternalImage
) = buildOutgoingPacket { ) = buildOutgoingPacket {
...@@ -256,9 +256,8 @@ object FriendImageIdRequestPacket : OutgoingPacketBuilder { ...@@ -256,9 +256,8 @@ object FriendImageIdRequestPacket : OutgoingPacketBuilder {
} }
@CorrespondingEvent(FriendImageIdObtainedEvent::class) @CorrespondingEvent(FriendImageIdObtainedEvent::class)
@AnnotatedId(KnownPacketId.FRIEND_IMAGE_ID)
@PacketVersion(date = "2019.11.1", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.11.1", timVersion = "2.3.2.21173")
class Response(input: ByteReadPacket) : ResponsePacket(input) { class Response : Packet {
/** /**
* 访问 HTTP API 时需要使用的一个 key. 128 位 * 访问 HTTP API 时需要使用的一个 key. 128 位
*/ */
...@@ -285,29 +284,29 @@ object FriendImageIdRequestPacket : OutgoingPacketBuilder { ...@@ -285,29 +284,29 @@ object FriendImageIdRequestPacket : OutgoingPacketBuilder {
*/ */
OVER_FILE_SIZE_MAX, OVER_FILE_SIZE_MAX,
} }
}
override fun decode() = with(input) { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response().apply {
discardExact(6) discardExact(6)
if (readUByte() != UByte.MIN_VALUE) { if (readUByte() != UByte.MIN_VALUE) {
discardExact(60) discardExact(60)
@Suppress("ControlFlowWithEmptyBody") @Suppress("ControlFlowWithEmptyBody")
while (readUByte().toUInt() != 0x4Au); while (readUByte().toUInt() != 0x4Au);
uKey = readBytes(readUnsignedVarInt().toInt())//128 uKey = readBytes(readUnsignedVarInt().toInt())//128
discardExact(1)//52, id discardExact(1)//52, id
imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37 imageId = ImageId(readString(readUnsignedVarInt().toInt()))//37
state = REQUIRE_UPLOAD state = REQUIRE_UPLOAD
} else {
val toDiscard = readUByte().toInt() - 37
if (toDiscard < 0) {
state = OVER_FILE_SIZE_MAX
} else { } else {
val toDiscard = readUByte().toInt() - 37 discardExact(toDiscard)
if (toDiscard < 0) { imageId = ImageId(readString(37))
state = OVER_FILE_SIZE_MAX state = ALREADY_EXISTS
} else {
discardExact(toDiscard)
imageId = ImageId(readString(37))
state = ALREADY_EXISTS
}
} }
} }
} }
...@@ -319,12 +318,12 @@ object FriendImageIdRequestPacket : OutgoingPacketBuilder { ...@@ -319,12 +318,12 @@ object FriendImageIdRequestPacket : OutgoingPacketBuilder {
*/ */
@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID) @AnnotatedId(KnownPacketId.GROUP_IMAGE_ID)
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
object GroupImageIdRequestPacket : OutgoingPacketBuilder { object GroupImageIdRequestPacket : SessionPacketFactory<GroupImageIdRequestPacket.Response>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
groupInternalId: GroupInternalId, groupInternalId: GroupInternalId,
image: ExternalImage, image: ExternalImage,
sessionKey: ByteArray sessionKey: SessionKey
) = buildOutgoingPacket { ) = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00") writeHex("04 00 00 00 01 01 01 00 00 68 20 00 00 00 00 00 00 00 00")
...@@ -379,9 +378,8 @@ object GroupImageIdRequestPacket : OutgoingPacketBuilder { ...@@ -379,9 +378,8 @@ object GroupImageIdRequestPacket : OutgoingPacketBuilder {
private val value0x6A: UByteArray = ubyteArrayOf(0x05u, 0x32u, 0x36u, 0x36u, 0x35u, 0x36u) private val value0x6A: UByteArray = ubyteArrayOf(0x05u, 0x32u, 0x36u, 0x36u, 0x35u, 0x36u)
@AnnotatedId(KnownPacketId.GROUP_IMAGE_ID)
@PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173") @PacketVersion(date = "2019.10.26", timVersion = "2.3.2.21173")
class Response(input: ByteReadPacket) : ResponsePacket(input) { class Response : Packet {
lateinit var state: State lateinit var state: State
/** /**
...@@ -403,19 +401,19 @@ object GroupImageIdRequestPacket : OutgoingPacketBuilder { ...@@ -403,19 +401,19 @@ object GroupImageIdRequestPacket : OutgoingPacketBuilder {
*/ */
OVER_FILE_SIZE_MAX, OVER_FILE_SIZE_MAX,
} }
}
override fun decode(): Unit = with(input) { override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): Response = Response().apply {
discardExact(6)//00 00 00 05 00 00 discardExact(6)//00 00 00 05 00 00
val length = remaining - 128 - 14
if (length < 0) {
state = if (readUShort().toUInt() == 0x0025u) State.OVER_FILE_SIZE_MAX else State.ALREADY_EXISTS
return@with
}
discardExact(length) val length = remaining - 128 - 14
uKey = readBytes(128) if (length < 0) {
state = State.REQUIRE_UPLOAD state = if (readUShort().toUInt() == 0x0025u) Response.State.OVER_FILE_SIZE_MAX else Response.State.ALREADY_EXISTS
return@apply
} }
discardExact(length)
uKey = readBytes(128)
state = Response.State.REQUIRE_UPLOAD
} }
} }
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
/**
* Android 客户端上线
*/
class ServerAndroidOnlineEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, eventIdentity)
/**
* Android 客户端下线
*/
class ServerAndroidOfflineEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, eventIdentity)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.event.events.FriendConversationInitializedEvent
import net.mamoe.mirai.event.events.MemberPermissionChangedEvent
import net.mamoe.mirai.network.protocol.tim.packet.CorrespondingEvent
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
import net.mamoe.mirai.utils.io.readString
import kotlin.properties.Delegates
/**
* 群文件上传
*/
class ServerGroupUploadFileEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) :
ServerEventPacket(input, eventIdentity) {
private lateinit var xmlMessage: String
override fun decode() {
this.input.discardExact(60)
val size = this.input.readShort().toInt()
this.input.discardExact(3)
xmlMessage = this.input.readString(size)
}//todo test
}
class GroupMemberNickChangedEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) :
ServerEventPacket(input, eventIdentity) {
private val groupId: UInt get() = eventIdentity.from
private val group: UInt get() = eventIdentity.from
override fun decode() {
// GroupId VarInt
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 00 00 F3 66 00 00 00 05 00 00 00 EE 00 00 00 05
// TODO ? 数据中没有哪个人的昵称改变了
}
}
/**
* 好友发起会话, 即在输入框输入了任意内容.
*/
@CorrespondingEvent(FriendConversationInitializedEvent::class)
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
class FriendConversationInitializedEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) :
ServerEventPacket(input, eventIdentity) {
var qq: UInt by Delegates.notNull()
// 00 00 00 00 3E 03 3F A2 00
override fun decode() = with(input) {
discardExact(4)// 00 00 00 00
qq = readUInt()
}
}
@CorrespondingEvent(MemberPermissionChangedEvent::class)
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
class GroupMemberPermissionChangedEventPacket internal constructor(
input: ByteReadPacket,
eventIdentity: EventPacketIdentity
) :
ServerEventPacket(input, eventIdentity) {
val groupId: UInt get() = eventIdentity.from
var qq: UInt = 0u
lateinit var kind: MemberPermissionChangedEvent.Kind
override fun decode(): Unit = with(input) {
// 群里一个人变成管理员:
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 76 E4 B8 DD 01
// 取消管理员
// 00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 00 76 E4 B8 DD 00
discardExact(remaining - 5)
qq = readUInt()
kind = when (readByte().toInt()) {
0x00 -> MemberPermissionChangedEvent.Kind.NO_LONGER_OPERATOR
0x01 -> MemberPermissionChangedEvent.Kind.BECOME_OPERATOR
else -> {
error("Could not determine permission change kind")
}
}
}
}
class ServerGroupUnknownChangedEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) :
ServerEventPacket(input, eventIdentity) {
override fun decode() = with(input) {
//00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 00 00 F3 55 00 00 00 05 00 00 00 E9 00 00 00 05
//00 00 00 08 00 0A 00 04 01 00 00 00 22 96 29 7B 01 01 00 00 F3 56 00 00 00 05 00 00 00 EA 00 00 00 05
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.event
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUInt
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.NullMessageChain
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.packet.PacketVersion
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.printTLVMap
import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readTLVMap
import net.mamoe.mirai.utils.io.readUShortLVByteArray
import kotlin.properties.Delegates
enum class SenderPermission {
OWNER,
OPERATOR,
MEMBER;
}
@Suppress("EXPERIMENTAL_API_USAGE")
@PacketVersion(date = "2019.11.2", timVersion = "2.3.2.21173")
class GroupMessageEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) :
ServerEventPacket(input, eventIdentity) {
var groupNumber: UInt by Delegates.notNull()
var qq: UInt by Delegates.notNull()
lateinit var senderName: String
/**
* 发送方权限.
*/
lateinit var senderPermission: SenderPermission
var message: MessageChain = NullMessageChain
override fun decode() = with(input) {
discardExact(31)
groupNumber = readUInt()
discardExact(1)
qq = readUInt()
discardExact(48)
readUShortLVByteArray()
discardExact(2)//2个0x00
message = readMessageChain()
val map = readTLVMap(true)
if (map.containsKey(18u)) {
map.getValue(18u).read {
val tlv = readTLVMap(true)
//tlv.printTLVMap("消息结尾 tag=18 的 TLV")
////群主的18: 05 00 04 00 00 00 03 08 00 04 00 00 00 04 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
//群主的 子map= {5=00 00 00 03, 8=00 00 00 04, 1=48 69 6D 31 38 38 6D 6F 65, 3=04, 4=00 00 00 08}
//管理员 子map= {5=00 00 00 03, 8=00 00 00 04, 2=65 6F 6D 38 38 31 6D 69 48, 3=02, 4=00 00 00 10}
//群成员 子map= {5=00 00 00 03, 8=00 00 00 04, 2=65 6F 6D 38 38 31 6D 69 48, 3=02}
// 4=08, 群主
// 没有4, 群员
// 4=10, 管理员
senderPermission = when (tlv.takeIf { it.containsKey(0x04u) }?.get(0x04u)?.getOrNull(3)?.toUInt()) {
null -> SenderPermission.MEMBER
0x08u -> SenderPermission.OWNER
0x10u -> SenderPermission.OPERATOR
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine member permission, default permission MEMBER is being used")
SenderPermission.MEMBER
}
}
senderName = when {
tlv.containsKey(0x01u) -> kotlinx.io.core.String(tlv.getValue(0x01u))//这个人的qq昵称
tlv.containsKey(0x02u) -> kotlinx.io.core.String(tlv.getValue(0x02u))//这个人的群名片
else -> {
tlv.printTLVMap("TLV(tag=18) Map")
MiraiLogger.warning("Could not determine senderName")
"null"
}
}
}
}
}
}
//
//以前的消息: 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
class FriendMessageEventPacket(input: ByteReadPacket, eventIdentity: EventPacketIdentity) :
ServerEventPacket(input, eventIdentity) {
val qq: UInt get() = eventIdentity.from
/**
* 是否是在这次登录之前的消息, 即消息记录
*/
var isPrevious: Boolean = false
var message: MessageChain by Delegates.notNull()
//来自自己发送给自己
//00 00 00 20 00 05 00 02 00 06 00 06 00 04 00 01 01 07 00 09 00 06 03 E9 20 02 EB 94 00 0A 00 04 01 00 00 00 0C 17 76 E4 B8 DD 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0B A6 D2 5D A3 2A 3F 00 00 5D A3 2A 3F 01 00 00 00 00 4D 53 47 00 00 00 00 00 5D A3 2A 3F 0C 8A 59 3D 00 00 00 00 0A 00 86 02 00 06 E5 AE 8B E4 BD 93 00 00 01 00 06 01 00 03 31 32 33 19 00 1F 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 0E 00 0E 01 00 04 00 00 00 00 0A 00 04 00 00 00 00
override fun decode() = with(input) {
input.discardExact(2)
val l1 = readShort()
discardExact(1)//0x00
isPrevious = readByte().toInt() == 0x08
discardExact(l1.toInt() - 2)
//java.io.EOFException: Only 49 bytes were discarded of 69 requested
//抖动窗口消息
discardExact(69)
readUShortLVByteArray()//font
discardExact(2)//2个0x00
message = readMessageChain()
//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
/*
val offset = unknownLength0 + fontLength//57
event = MessageChain(PlainText(let {
val length = input.readShortAt(101 + offset)//
input.goto(103 + offset).readString(length.toInt())
}))*/
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS", "unused", "FunctionName")
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
/** object CaptchaKey : DecrypterByteArray, DecrypterType<CaptchaKey> {
* 客户端请求验证码图片数据的第几部分 override val value: ByteArray = TIMProtocol.key00BA.hexToBytes(withCache = false)
*/ }
@AnnotatedId(KnownPacketId.CAPTCHA) @AnnotatedId(KnownPacketId.CAPTCHA)
object RequestCaptchaTransmissionPacket : OutgoingPacketBuilder { object CaptchaPacket : PacketFactory<CaptchaPacket.CaptchaResponse, CaptchaKey>(CaptchaKey) {
operator fun invoke( /**
* 请求验证码传输
*/
fun RequestTransmission(
bot: UInt, bot: UInt,
token0825: ByteArray, token0825: ByteArray,
captchaSequence: Int, captchaSequence: Int,
token00BA: ByteArray token00BA: ByteArray
) = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer) writeHex(TIMProtocol.fixVer)
writeHex(TIMProtocol.key00BA) writeHex(TIMProtocol.key00BA)
...@@ -36,19 +41,38 @@ object RequestCaptchaTransmissionPacket : OutgoingPacketBuilder { ...@@ -36,19 +41,38 @@ object RequestCaptchaTransmissionPacket : OutgoingPacketBuilder {
writeHex(TIMProtocol.key00BAFix) writeHex(TIMProtocol.key00BAFix)
} }
} }
}
/** /**
* 提交验证码 * 刷新验证码
*/ */
@AnnotatedId(KnownPacketId.CAPTCHA) fun Refresh(
object SubmitCaptchaPacket : OutgoingPacketBuilder { bot: UInt,
operator fun invoke( token0825: ByteArray
): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot)
writeHex(TIMProtocol.fixVer)
writeHex(TIMProtocol.key00BA)
encryptAndWrite(TIMProtocol.key00BA) {
writeHex("00 02 00 00 08 04 01 E0")
writeHex(TIMProtocol.constantData2)
writeHex("00 00 38")
writeFully(token0825)
writeHex("01 03 00 19")
writeHex(TIMProtocol.publicKey)
writeHex("13 00 05 00 00 00 00 00 00 00 00 10")
writeHex(TIMProtocol.key00BAFix)
}
}
/**
* 提交验证码
*/
fun Submit(
bot: UInt, bot: UInt,
token0825: ByteArray, token0825: ByteArray,
captcha: String, captcha: String,
captchaToken: IoBuffer captchaToken: IoBuffer
) = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
require(captcha.length == 4) { "captcha.length must == 4" } require(captcha.length == 4) { "captcha.length must == 4" }
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer) writeHex(TIMProtocol.fixVer)
...@@ -72,99 +96,52 @@ object SubmitCaptchaPacket : OutgoingPacketBuilder { ...@@ -72,99 +96,52 @@ object SubmitCaptchaPacket : OutgoingPacketBuilder {
writeHex(TIMProtocol.key00BAFix)//16 writeHex(TIMProtocol.key00BAFix)//16
} }
} }
}
/** sealed class CaptchaResponse : Packet {
* 刷新验证码 lateinit var token00BA: ByteArray//56 bytes
*/
@AnnotatedId(KnownPacketId.CAPTCHA) class Correct : CaptchaResponse()
object OutgoingCaptchaRefreshPacket : OutgoingPacketBuilder {
operator fun invoke( class Transmission : CaptchaResponse() {
qq: UInt, lateinit var captchaSectionN: IoBuffer
token0825: ByteArray lateinit var captchaToken: IoBuffer//56bytes
) = buildOutgoingPacket { var transmissionCompleted: Boolean = false//验证码是否已经传输完成
writeQQ(qq)
writeHex(TIMProtocol.fixVer)
writeHex(TIMProtocol.key00BA)
encryptAndWrite(TIMProtocol.key00BA) {
writeHex("00 02 00 00 08 04 01 E0")
writeHex(TIMProtocol.constantData2)
writeHex("00 00 38")
writeFully(token0825)
writeHex("01 03 00 19")
writeHex(TIMProtocol.publicKey)
writeHex("13 00 05 00 00 00 00 00 00 00 00 10")
writeHex(TIMProtocol.key00BAFix)
} }
} }
}
/** override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): CaptchaResponse =
* 服务器发送验证码图片文件一部分过来. 当验证码输入错误时, 服务器的返回也会是这个包. when (val id = readByte().toUInt()) {
* 0x14u -> {//00 05 00 00 00 00 00 00 38
* @author Him188moe CaptchaResponse.Correct().apply {
*/ discardExact(9)
@AnnotatedId(KnownPacketId.CAPTCHA) token00BA = readBytes(56)
open class CaptchaTransmissionResponsePacket(input: ByteReadPacket) : ServerCaptchaPacket(input) { }
lateinit var captchaSectionN: IoBuffer }
lateinit var captchaToken: IoBuffer//56bytes 0x13u -> {
var transmissionCompleted: Boolean = false//验证码是否已经传输完成 CaptchaResponse.Transmission().apply {
lateinit var token00BA: ByteArray//40 bytes discardExact(9)
captchaToken = readIoBuffer(56)
override fun decode() = with(input) { val length = readShort()
input.discardExact(10)//13 00 05 01 00 00 01 23 00 38 captchaSectionN = readIoBuffer(length)
captchaToken = readIoBuffer(56)
val length = readShort() discardExact(1)
captchaSectionN = readIoBuffer(length) val byte = readByte().toInt()
transmissionCompleted = byte == 0
discardExact(1) discardExact(remaining - 56 - 2)
val byte = readByte().toInt() token00BA = readBytes(40)
transmissionCompleted = byte == 0 }
}
discardExact(remaining - 56 - 2) else -> error("Unable to analyze RequestCaptchaTransmissionPacket, unknown id: $id")
token00BA = readBytes(40) }
}
} }
/* /*
fun main() { fun main() {
val data = "13 00 05 01 00 00 01 23 00 38 59 32 29 5A 3E 3D 2D FC F5 22 EB 9E 2D FB 9C 4F AA 06 C8 32 3D F0 3C 2C 2B BA 8D 05 C4 9B C1 74 3B 70 F1 99 90 BB 6E 3E 6F 74 48 97 D3 61 B7 04 C0 A3 F1 DF 40 A4 DC 2B 00 A2 01 2D BB BB E8 FE B8 AF B3 6F 39 7C EA E2 5B 91 BE DB 59 38 CF 58 BC F2 88 F1 09 CF 92 E9 F7 FB 13 76 C5 68 29 23 3F 8E 43 16 2E 50 D7 FA 4D C1 F7 67 EF 27 FB C6 F1 A7 25 A4 BC 45 39 3A EA B2 A5 38 02 FF 4B C9 FF EB BD 89 E5 5D B9 4A 2A BE 5F 52 F1 EB 09 29 CB 3E 66 CF EF 97 89 47 BB 6B E0 7B 4A 3E A1 BC 3F FB F2 0A 83 CB E3 EA B9 43 E1 26 88 03 0B A7 E0 B2 AD 7F 83 CC DA 74 85 83 72 08 EC D2 F9 95 05 15 05 96 F7 1C FF 00 82 C3 90 22 A4 BA 90 D5 00 00 00 00 49 45 4E 44 AE 42 60 82 03 00 00 28 EA 32 5A 85 C8 D2 73 B3 40 39 77 85 65 98 00 FE 03 A2 A5 95 B4 2F E6 79 7A DE 5A 03 10 C8 3D BF 6D 3D 8B 51 84 C2 6D 49 00 10 92 AA 69 FB C6 3D 60 5A 7A A4 AC 7A B0 71 00 36".hexToBytes() val data = "13 00 05 01 00 00 01 23 00 38 59 32 29 5A 3E 3D 2D FC F5 22 EB 9E 2D FB 9C 4F AA 06 C8 32 3D F0 3C 2C 2B BA 8D 05 C4 9B C1 74 3B 70 F1 99 90 BB 6E 3E 6F 74 48 97 D3 61 B7 04 C0 A3 F1 DF 40 A4 DC 2B 00 A2 01 2D BB BB E8 FE B8 AF B3 6F 39 7C EA E2 5B 91 BE DB 59 38 CF 58 BC F2 88 F1 09 CF 92 E9 F7 FB 13 76 C5 68 29 23 3F 8E 43 16 2E 50 D7 FA 4D C1 F7 67 EF 27 FB C6 F1 A7 25 A4 BC 45 39 3A EA B2 A5 38 02 FF 4B C9 FF EB BD 89 E5 5D B9 4A 2A BE 5F 52 F1 EB 09 29 CB 3E 66 CF EF 97 89 47 BB 6B E0 7B 4A 3E A1 BC 3F FB F2 0A 83 CB E3 EA B9 43 E1 26 88 03 0B A7 E0 B2 AD 7F 83 CC DA 74 85 83 72 08 EC D2 F9 95 05 15 05 96 F7 1C FF 00 82 C3 90 22 A4 BA 90 D5 00 00 00 00 49 45 4E 44 AE 42 60 82 03 00 00 28 EA 32 5A 85 C8 D2 73 B3 40 39 77 85 65 98 00 FE 03 A2 A5 95 B4 2F E6 79 7A DE 5A 03 10 C8 3D BF 6D 3D 8B 51 84 C2 6D 49 00 10 92 AA 69 FB C6 3D 60 5A 7A A4 AC 7A B0 71 00 36".hexToBytes()
CaptchaTransmissionResponsePacket(data.toReadPacket(), data.size, "00 BA 31 01".hexToBytes()).let { ServerCaptchaTransmissionResponsePacket(data.toReadPacket(), data.size, "00 BA 31 01".hexToBytes()).let {
it.dataDecode() it.dataDecode()
println(it.toString()) println(it.toString())
} }
}*/ }*/
\ No newline at end of file
/**
* 验证码正确
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.CAPTCHA)
class CaptchaCorrectPacket(input: ByteReadPacket) : ServerCaptchaPacket(input) {
lateinit var token00BA: ByteArray//56 bytes
override fun decode() = with(input) {
discardExact(10)//14 00 05 00 00 00 00 00 00 38
token00BA = readBytes(56)
}
}
@AnnotatedId(KnownPacketId.CAPTCHA)
abstract class ServerCaptchaPacket(input: ByteReadPacket) : ServerPacket(input) {
@AnnotatedId(KnownPacketId.CAPTCHA)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): ServerCaptchaPacket {
return this.decryptAsByteArray(TIMProtocol.key00BA) { data ->
when (data.size) {
66,
95 -> CaptchaCorrectPacket(data.toReadPacket())
//66 -> ServerCaptchaUnknownPacket(data.toReadPacket())
else -> CaptchaTransmissionResponsePacket(data.toReadPacket())
}.applySequence(sequenceId)
}
}
}
}
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeUByte import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.OnlineStatus import net.mamoe.mirai.utils.OnlineStatus
...@@ -14,10 +16,10 @@ import net.mamoe.mirai.utils.io.writeQQ ...@@ -14,10 +16,10 @@ import net.mamoe.mirai.utils.io.writeQQ
* 改变在线状态: "我在线上", "隐身" 等 * 改变在线状态: "我在线上", "隐身" 等
*/ */
@AnnotatedId(KnownPacketId.CHANGE_ONLINE_STATUS) @AnnotatedId(KnownPacketId.CHANGE_ONLINE_STATUS)
object ChangeOnlineStatusPacket : OutgoingPacketBuilder { object ChangeOnlineStatusPacket : PacketFactory<ChangeOnlineStatusPacket.ChangeOnlineStatusResponse, NoDecrypter>(NoDecrypter) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
sessionKey: ByteArray, sessionKey: SessionKey,
loginStatus: OnlineStatus loginStatus: OnlineStatus
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
...@@ -28,4 +30,9 @@ object ChangeOnlineStatusPacket : OutgoingPacketBuilder { ...@@ -28,4 +30,9 @@ object ChangeOnlineStatusPacket : OutgoingPacketBuilder {
writeHex("00 01 00 01 00 04 00 00 00 00") writeHex("00 01 00 01 00 04 00 00 00 00")
} }
} }
object ChangeOnlineStatusResponse : Packet
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): ChangeOnlineStatusResponse =
ChangeOnlineStatusResponse
} }
\ No newline at end of file
...@@ -2,26 +2,52 @@ ...@@ -2,26 +2,52 @@
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder import kotlinx.io.core.*
import kotlinx.io.core.IoBuffer import net.mamoe.mirai.contact.Gender
import kotlinx.io.core.writeFully import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.decryptBy
import net.mamoe.mirai.utils.encryptBy import net.mamoe.mirai.utils.encryptBy
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.writeCRC32 import net.mamoe.mirai.utils.writeCRC32
import kotlin.properties.Delegates
object ShareKey : DecrypterByteArray, DecrypterType<ShareKey> {
override val value: ByteArray = TIMProtocol.shareKey.hexToBytes(withCache = false)
}
inline class PrivateKey(override val value: ByteArray) : DecrypterByteArray {
companion object Type : DecrypterType<PrivateKey>
}
inline class SubmitPasswordResponseDecrypter(private val privateKey: PrivateKey) : Decrypter {
override fun decrypt(packet: ByteReadPacket): ByteReadPacket {
var decrypted = ShareKey.decrypt(packet)
(decrypted.remaining).let {
if (it.toInt() % 8 == 0 && it >= 16) {
decrypted = privateKey.decrypt(decrypted)
}
} // TODO: 2019/11/5 优化: 某些情况下并不需要这次解密. 根据长度判断会导致一些问题
return decrypted
}
companion object Type : DecrypterType<SubmitPasswordResponseDecrypter>
}
/** /**
* 提交密码 * 提交密码
*/ */
@AnnotatedId(KnownPacketId.LOGIN) @AnnotatedId(KnownPacketId.LOGIN)
object SubmitPasswordPacket : OutgoingPacketBuilder { object SubmitPasswordPacket : PacketFactory<SubmitPasswordPacket.LoginResponse, SubmitPasswordResponseDecrypter>(SubmitPasswordResponseDecrypter) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
password: String, password: String,
loginTime: Int, loginTime: Int,
loginIP: String, loginIP: String,
privateKey: ByteArray, privateKey: PrivateKey,
token0825: ByteArray, token0825: ByteArray,
token00BA: ByteArray? = null, token00BA: ByteArray? = null,
randomDeviceName: Boolean = false, randomDeviceName: Boolean = false,
...@@ -29,38 +55,168 @@ object SubmitPasswordPacket : OutgoingPacketBuilder { ...@@ -29,38 +55,168 @@ object SubmitPasswordPacket : OutgoingPacketBuilder {
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.passwordSubmissionTLV1) writeHex(TIMProtocol.passwordSubmissionTLV1)
writeShort(25); writeHex(TIMProtocol.publicKey)//=25
writeShort(25)
writeHex(TIMProtocol.publicKey)//=25
writeZero(2) writeZero(2)
writeShort(16) writeShort(16); writeHex(TIMProtocol.key0836)//=16
writeHex(TIMProtocol.key0836)//=16
//TODO shareKey 极大可能为 publicKey, key0836 计算得到 //TODO shareKey 极大可能为 publicKey, key0836 计算得到
encryptAndWrite(TIMProtocol.shareKey) { encryptAndWrite(TIMProtocol.shareKey) {
writePart1(bot, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006) writePart1(bot, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006)
if (token00BA != null) { if (token00BA != null) {
writeHex("01 10") writeHex("01 10")
writeHex("00 3C") writeHex("00 3C")
writeHex("00 01") writeHex("00 01")
writeHex("00 38"); writeFully(token00BA)
}
writePart2()
}
}
sealed class LoginResponse : Packet {
class KeyExchange : LoginResponse() {
lateinit var tlv0006: IoBuffer//120bytes
var tokenUnknown: ByteArray? = null
var privateKeyUpdate: PrivateKey? = null//16bytes
}
class CaptchaInit : LoginResponse() {
lateinit var captchaPart1: IoBuffer
lateinit var token00BA: ByteArray
var unknownBoolean: Boolean by Delegates.notNull()
}
class Success : LoginResponse() {
var sessionResponseDecryptionKey: SessionResponseDecryptionKey by Delegates.notNull()//16 bytes|
lateinit var token38: IoBuffer//56
lateinit var token88: IoBuffer//136
lateinit var encryptionKey: IoBuffer//16
lateinit var nickname: String
var age: Short by Delegates.notNull()
lateinit var gender: Gender
}
writeHex("00 38") class Failed(val result: LoginResult) : LoginResponse()
writeFully(token00BA) }
override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): LoginResponse {
val size = remaining.toInt()
return when {
size == 229 || size == 271 || size == 207 -> LoginResponse.KeyExchange().apply {
discardExact(5)//01 00 1E 00 10
privateKeyUpdate = PrivateKey(readBytes(0x10))
discardExact(4)//00 06 00 78
tlv0006 = readIoBuffer(0x78)
try {
discardExact(8)//01 10 00 3C 00 01 00 38
tokenUnknown = readBytes(56)
} catch (e: EOFException) {
//什么都不做. 因为有的包就是没有这个数据.
}
} }
writePart2() size == 844 || size == 871 -> LoginResponse.CaptchaInit().apply {
discardExact(78)
//println(readRemainingBytes().toUHexString())
val captchaLength = readShort()//2bytes
this.captchaPart1 = readIoBuffer(captchaLength)
discardExact(1)
this.unknownBoolean = readByte().toInt() == 1
discardExact(remaining - 60)
this.token00BA = readBytes(40)
}
size > 650 -> LoginResponse.Success().apply {
discardExact(7)//00 01 09 00 70 00 01
//FB 01 04 03 33
encryptionKey = readIoBuffer(16)//C6 72 C7 73 70 01 46 A2 11 88 AC E4 92 7B BF 90
discardExact(2)//00 38
token38 = readIoBuffer(56)
discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6
discardExact(
when (readUByte().toUInt()) {
0x00u -> when (readUByte().toUInt()) {
0x33u -> 28
else -> null
}
0x01u -> when (readUByte().toUInt()) {
0x07u -> 0
0x10u -> 64
else -> null
}
else -> null
} ?: error("Unknown length flag")
)
discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00
discardExact(2)//00 02
sessionResponseDecryptionKey = SessionResponseDecryptionKey(readIoBuffer(16))
discardExact(2)
token88 = readIoBuffer(136)
discardExact(299)//2E 72 7A 50 41 54 5B 62 7D 47 5D 37 41 53 47 51 00 78 00 01 5D A2 DB 79 00 70 72 E7 D3 4E 6F D8 D1 DD F2 67 04 1D 23 4D E9 A7 AB 89 7A B7 E6 4B C0 79 60 3B 4F AA 31 C5 24 51 C1 4B 4F A4 32 74 BA FE 8E 06 DB 54 25 A2 56 91 E8 66 BB 23 29 EB F7 13 7B 94 1E AF B2 40 4E 69 5C 8C 35 04 D1 25 1F 60 93 F3 40 71 0B 61 60 F1 B6 A9 7A E8 B1 DA 0E 16 A2 F1 2D 69 5A 01 20 7A AB A7 37 68 D2 1A B0 4D 35 D1 E1 35 64 F6 90 2B 00 83 01 24 5B 4E 69 3D 45 54 6B 29 5E 73 23 2D 4E 42 3F 00 70 00 01 5D A2 DB 79 00 68 FD 10 8A 39 51 09 C6 69 CE 09 A4 52 8C 53 D3 B6 87 E1 7B 7E 4E 52 6D BA 9C C4 6E 6D DE 09 99 67 B4 BD 56 71 14 5A 54 01 68 1C 3C AA 0D 76 0B 86 5A C1 F1 BC 5E 0A ED E3 8C 57 86 35 D8 A5 F8 16 01 24 8B 57 56 8C A6 31 6F 65 73 03 DA ED 21 FA 6B 79 32 2B 09 01 E8 D2 D8 F0 7B F1 60 C2 7F 53 5D F6 53 50 8A 43 E2 23 2E 52 7B 60 39 56 67 2D 6A 23 43 4B 60 55 68 35 01 08 00 23 00 01 00 1F 00 17 02 5B
val nickLength = readUByte().toInt()
nickname = readString(nickLength)
//后文
//00 05 00 04 00 00 00 01 01 15 00 10 49 83 5C D9 93 6C 8D FE 09 18 99 37 99 80 68 92
discardExact(4)//02 13 80 02
age = readShort()//00 05
discardExact(4)//00 04 00 00
discardExact(2)//00 01
gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
}
else -> LoginResponse.Failed(when (size) {
135 -> {//包数据错误. 目前怀疑是 tlv0006
this.readRemainingBytes().cutTail(1).decryptBy(TIMProtocol.shareKey).read {
discardExact(51)
MiraiLogger.error("Internal error: " + readUShortLVString())//抱歉,请重新输入密码。
}
LoginResult.INTERNAL_ERROR
}
240, 319, 320, 351 -> LoginResult.WRONG_PASSWORD
//135 -> LoginState.RETYPE_PASSWORD
63 -> LoginResult.BLOCKED
263 -> LoginResult.UNKNOWN_QQ_NUMBER
279, 495, 551, 487 -> LoginResult.DEVICE_LOCK
343, 359 -> LoginResult.TAKEN_BACK
else -> {
MiraiLogger.error("login response packet size = $size, data=${this.readRemainingBytes().toUHexString()}")
LoginResult.UNKNOWN
}
})
} }
} }
} }
inline class SessionResponseDecryptionKey(private val delegate: IoBuffer) : Decrypter {
override fun decrypt(packet: ByteReadPacket): ByteReadPacket = packet.decryptBy(delegate)
companion object Type : DecrypterType<SessionResponseDecryptionKey>
}
private fun BytePacketBuilder.writePart1( private fun BytePacketBuilder.writePart1(
qq: UInt, qq: UInt,
password: String, password: String,
loginTime: Int, loginTime: Int,
loginIP: String, loginIP: String,
privateKey: ByteArray, privateKey: PrivateKey,
token0825: ByteArray, token0825: ByteArray,
randomDeviceName: Boolean, randomDeviceName: Boolean,
tlv0006: IoBuffer? = null tlv0006: IoBuffer? = null
......
...@@ -4,6 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet.login ...@@ -4,6 +4,7 @@ package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.BotSession import net.mamoe.mirai.network.BotSession
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
...@@ -17,10 +18,10 @@ fun BotSession.RequestSKeyPacket(): OutgoingPacket = RequestSKeyPacket(qqAccount ...@@ -17,10 +18,10 @@ fun BotSession.RequestSKeyPacket(): OutgoingPacket = RequestSKeyPacket(qqAccount
* SKey 用于 http api * SKey 用于 http api
*/ */
@AnnotatedId(KnownPacketId.S_KEY) @AnnotatedId(KnownPacketId.S_KEY)
object RequestSKeyPacket : OutgoingPacketBuilder { object RequestSKeyPacket : SessionPacketFactory<SKey>() {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
sessionKey: ByteArray sessionKey: SessionKey
): OutgoingPacket = buildOutgoingPacket { ): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer2) writeHex(TIMProtocol.fixVer2)
...@@ -29,16 +30,15 @@ object RequestSKeyPacket : OutgoingPacketBuilder { ...@@ -29,16 +30,15 @@ object RequestSKeyPacket : OutgoingPacketBuilder {
} }
} }
@AnnotatedId(KnownPacketId.S_KEY) override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): SKey {
class Response(input: ByteReadPacket) : ResponsePacket(input) { discardExact(4)
lateinit var sKey: String // TODO: 2019/11/2 这里
return SKey(readString(10)).also {
override fun decode() = with(input) { DebugLogger.warning("SKey 包后面${readRemainingBytes().toUHexString()}")
discardExact(4)
//debugDiscardExact(2)
sKey = this.readString(10)//16??
DebugLogger.warning("SKey=$sKey")
DebugLogger.warning("SKey 包后面${this.readRemainingBytes().toUHexString()}")
} }
} }
} }
\ No newline at end of file
inline class SKey(
val delegate: String
) : Packet
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.contact.Gender
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.io.readBoolean
import net.mamoe.mirai.utils.io.readIoBuffer
import net.mamoe.mirai.utils.io.readString
import kotlin.properties.Delegates
@AnnotatedId(KnownPacketId.LOGIN)
sealed class ServerLoginResponsePacket(input: ByteReadPacket) : ServerPacket(input)
@AnnotatedId(KnownPacketId.LOGIN)
class LoginResponseFailedPacket(val loginResult: LoginResult, input: ByteReadPacket) : ServerLoginResponsePacket(input)
/**
* 服务器进行加密后返回 privateKey
*/
@AnnotatedId(KnownPacketId.LOGIN)
class LoginResponseKeyExchangeResponsePacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var tlv0006: IoBuffer//120bytes
var tokenUnknown: ByteArray? = null
lateinit var privateKeyUpdate: ByteArray//16bytes
@Tested
override fun decode() {
this.input.discardExact(5)//01 00 1E 00 10
privateKeyUpdate = this.input.readBytes(0x10)
this.input.discardExact(4)//00 06 00 78
tlv0006 = this.input.readIoBuffer(0x78)
try {
this.input.discardExact(8)//01 10 00 3C 00 01 00 38
tokenUnknown = this.input.readBytes(56)
} catch (e: EOFException) {
//什么都不做. 因为有的包就是没有这个数据.
}
}
@AnnotatedId(KnownPacketId.LOGIN)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
@Tested
fun decrypt(privateKey: ByteArray): LoginResponseKeyExchangeResponsePacket =
LoginResponseKeyExchangeResponsePacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).applySequence(sequenceId)
}
}
@AnnotatedId(KnownPacketId.LOGIN)
class LoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var sessionResponseDecryptionKey: IoBuffer//16 bytes|
lateinit var token38: IoBuffer//56
lateinit var token88: IoBuffer//136
lateinit var encryptionKey: IoBuffer//16
lateinit var nickname: String
var age: Short by Delegates.notNull()
lateinit var gender: Gender
@Tested
override fun decode() = with(input) {
discardExact(7)//00 01 09 00 70 00 01
encryptionKey = readIoBuffer(16)//C6 72 C7 73 70 01 46 A2 11 88 AC E4 92 7B BF 90
discardExact(2)//00 38
token38 = readIoBuffer(56)
discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6
discardExact(
when (readUByte().toUInt()) {
0x00u -> when (readUByte().toUInt()) {
0x33u -> 28
else -> null
}
0x01u -> when (readUByte().toUInt()) {
0x07u -> 0
0x10u -> 64
else -> null
}
else -> null
} ?: error("Unknown length flag")
)
discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00
discardExact(2)//00 02
sessionResponseDecryptionKey = readIoBuffer(16)
discardExact(2)
token88 = readIoBuffer(136)
discardExact(299)//2E 72 7A 50 41 54 5B 62 7D 47 5D 37 41 53 47 51 00 78 00 01 5D A2 DB 79 00 70 72 E7 D3 4E 6F D8 D1 DD F2 67 04 1D 23 4D E9 A7 AB 89 7A B7 E6 4B C0 79 60 3B 4F AA 31 C5 24 51 C1 4B 4F A4 32 74 BA FE 8E 06 DB 54 25 A2 56 91 E8 66 BB 23 29 EB F7 13 7B 94 1E AF B2 40 4E 69 5C 8C 35 04 D1 25 1F 60 93 F3 40 71 0B 61 60 F1 B6 A9 7A E8 B1 DA 0E 16 A2 F1 2D 69 5A 01 20 7A AB A7 37 68 D2 1A B0 4D 35 D1 E1 35 64 F6 90 2B 00 83 01 24 5B 4E 69 3D 45 54 6B 29 5E 73 23 2D 4E 42 3F 00 70 00 01 5D A2 DB 79 00 68 FD 10 8A 39 51 09 C6 69 CE 09 A4 52 8C 53 D3 B6 87 E1 7B 7E 4E 52 6D BA 9C C4 6E 6D DE 09 99 67 B4 BD 56 71 14 5A 54 01 68 1C 3C AA 0D 76 0B 86 5A C1 F1 BC 5E 0A ED E3 8C 57 86 35 D8 A5 F8 16 01 24 8B 57 56 8C A6 31 6F 65 73 03 DA ED 21 FA 6B 79 32 2B 09 01 E8 D2 D8 F0 7B F1 60 C2 7F 53 5D F6 53 50 8A 43 E2 23 2E 52 7B 60 39 56 67 2D 6A 23 43 4B 60 55 68 35 01 08 00 23 00 01 00 1F 00 17 02 5B
val nickLength = readUByte().toInt()
nickname = readString(nickLength)
//后文
//00 05 00 04 00 00 00 01 01 15 00 10 49 83 5C D9 93 6C 8D FE 09 18 99 37 99 80 68 92
discardExact(4)//02 13 80 02
age = readShort()//00 05
discardExact(4)//00 04 00 00
discardExact(2)//00 01
gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
}
@AnnotatedId(KnownPacketId.LOGIN)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(privateKey: ByteArray): LoginResponseSuccessPacket = LoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).applySequence(sequenceId)
}
}
/**
* 收到这个包意味着需要验证码登录, 并且能得到验证码图片文件的一部分
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.LOGIN)
class LoginResponseCaptchaInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var captchaPart1: IoBuffer
lateinit var token00BA: ByteArray
var unknownBoolean: Boolean by Delegates.notNull()
@Tested
override fun decode() {
this.input.discardExact(78)
//println(this.input.readRemainingBytes().toUHexString())
val captchaLength = this.input.readShort()//2bytes
this.captchaPart1 = this.input.readIoBuffer(captchaLength)
this.input.discardExact(1)
this.unknownBoolean = this.input.readByte().toInt() == 1
this.input.discardExact(this.input.remaining - 60)
this.token00BA = this.input.readBytes(40)
}
@AnnotatedId(KnownPacketId.LOGIN)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): LoginResponseCaptchaInitPacket = LoginResponseCaptchaInitPacket(decryptBy(TIMProtocol.shareKey)).applySequence(sequenceId)
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.packet.AnnotatedId
import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
/**
* Congratulations!
*
* @author Him188moe
*/
@AnnotatedId(KnownPacketId.CHANGE_ONLINE_STATUS)
class ServerLoginSuccessPacket(input: ByteReadPacket) : ServerPacket(input)//TODO 可能只是 login status change 的返回包
\ No newline at end of file
...@@ -3,14 +3,14 @@ ...@@ -3,14 +3,14 @@
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.localIpAddress import net.mamoe.mirai.utils.localIpAddress
@AnnotatedId(KnownPacketId.SESSION_KEY) @AnnotatedId(KnownPacketId.SESSION_KEY)
object RequestSessionPacket : OutgoingPacketBuilder { object RequestSessionPacket : PacketFactory<RequestSessionPacket.SessionKeyResponse, SessionResponseDecryptionKey>(SessionResponseDecryptionKey) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
serverIp: String, serverIp: String,
...@@ -58,61 +58,49 @@ object RequestSessionPacket : OutgoingPacketBuilder { ...@@ -58,61 +58,49 @@ object RequestSessionPacket : OutgoingPacketBuilder {
writeIP(localIpAddress())//todo random to avoid being banned? or that may cause errors? writeIP(localIpAddress())//todo random to avoid being banned? or that may cause errors?
} }
} }
}
class SessionKeyResponse : Packet {
var sessionKey: SessionKey? = null
lateinit var tlv0105: ByteReadPacket
}
@AnnotatedId(KnownPacketId.SESSION_KEY) override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): SessionKeyResponse =
class SessionKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) { SessionKeyResponse().apply {
lateinit var sessionKey: ByteArray when (remaining) {
lateinit var tlv0105: ByteReadPacket
@Tested
override fun decode() = with(input) {
when (val dataLength = remaining) {
407L -> { 407L -> {
input.discardExact(25)//todo test discardExact(25)//todo test
sessionKey = input.readBytes(16) sessionKey = SessionKey(readBytes(16))
} }
439L -> { 439L -> {
input.discardExact(63) discardExact(63)
sessionKey = input.readBytes(16) sessionKey = SessionKey(readBytes(16))
} }
502L,//? 502L,//?
512L, 512L,
527L -> { 527L -> {
input.discardExact(63)//00 00 0D 00 06 00 01 00 00 00 00 00 1F 00 22 00 01 D7 EC FC 38 1B 74 6F 91 42 00 B9 DB 69 32 43 EC 8C 02 DC E0 07 35 58 8C 6C FE 43 5D AA 6A 88 E0 00 14 00 04 00 01 00 3C 01 0C 00 73 00 01 discardExact(63)//00 00 0D 00 06 00 01 00 00 00 00 00 1F 00 22 00 01 D7 EC FC 38 1B 74 6F 91 42 00 B9 DB 69 32 43 EC 8C 02 DC E0 07 35 58 8C 6C FE 43 5D AA 6A 88 E0 00 14 00 04 00 01 00 3C 01 0C 00 73 00 01
sessionKey = input.readBytes(16) sessionKey = SessionKey(readBytes(16))
tlv0105 = buildPacket { tlv0105 = buildPacket {
writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00") writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00")
input.discardExact(input.remaining - 122 - 1) discardExact(remaining - 122 - 1)
writeFully(input.readIoBuffer(56)) writeFully(readIoBuffer(56))
writeHex("00 40 02 02 03 3C 01 03 00 00") writeHex("00 40 02 02 03 3C 01 03 00 00")
input.discardExact(11) discardExact(11)
writeFully(input.readIoBuffer(56)) writeFully(readIoBuffer(56))
} //todo 这个 tlv0105似乎可以保存起来然后下次登录时使用. } //todo 这个 tlv0105似乎可以保存起来然后下次登录时使用.
/* /*
Discarded(63) =00 00 0D 00 06 00 01 00 00 00 00 00 1F 00 22 00 01 F7 AB 01 4B 23 B5 47 FC 79 02 09 E0 19 EF 61 91 14 AD 8F 38 2E 8B D7 47 39 DE FE 84 A7 E5 6E 3D 00 14 00 04 00 01 00 3C 01 0C 00 73 00 01 Discarded(63) =00 00 0D 00 06 00 01 00 00 00 00 00 1F 00 22 00 01 F7 AB 01 4B 23 B5 47 FC 79 02 09 E0 19 EF 61 91 14 AD 8F 38 2E 8B D7 47 39 DE FE 84 A7 E5 6E 3D 00 14 00 04 00 01 00 3C 01 0C 00 73 00 01
sessionKey=7E 8C 1D AC 52 64 B8 D0 9A 55 3A A6 DF 53 88 C8 sessionKey=7E 8C 1D AC 52 64 B8 D0 9A 55 3A A6 DF 53 88 C8
Discarded(301) =76 E4 B8 DD AB 53 02 2B 53 F1 5D A2 DA CB 00 00 00 B4 03 3D 97 B4 D1 3D 97 B4 C7 00 00 00 07 00 30 D4 E2 53 73 2E 00 F6 3F 8E 45 9F 2E 74 63 39 99 B4 AC 3B 40 C8 9A EE B0 62 A8 E1 39 FE 8E 75 EC 28 6C 03 E6 3B 5F F5 6D 50 7D 1E 29 EC 3D 47 85 08 02 04 08 08 08 08 08 04 00 05 01 0E 12 AC F6 01 0E 00 56 00 01 00 52 13 80 42 00 00 02 02 00 00 18 AB 52 CF 5B E8 CD 95 CC 3F 5C A7 BA C9 C1 5D DD F8 E2 6E 0D A3 DF F8 76 00 20 D3 87 6B 1F F2 2B C7 53 38 60 F3 AD 07 82 8B F6 62 3C E0 DB 66 BC AD D0 68 D0 30 9D 8A 41 E7 75 00 0C 00 00 00 01 00 00 00 00 00 00 00 40 00 2F 00 2A 00 01 8F FE 4F BB B2 63 C7 69 C3 F1 3C DC A1 E8 77 A3 DD 97 FA 00 36 04 40 EF 11 7A 31 02 4E 10 13 94 02 28 00 00 00 00 00 00 01 0D 00 2C 00 01 00 28 EF CB 22 58 6F AE DC F5 CC CE 45 EE 6D CA E7 EF 06 3F 60 B5 8A 22 D5 9E 37 FA 92 9F A9 11 68 F0 2A 25 4A 45 C3 D4 56 CF 01 05 00 8A 00 01 02 02 00 41 01 00 01 03 3C 01 03 00 00 FB Discarded(301) =76 E4 B8 DD AB 53 02 2B 53 F1 5D A2 DA CB 00 00 00 B4 03 3D 97 B4 D1 3D 97 B4 C7 00 00 00 07 00 30 D4 E2 53 73 2E 00 F6 3F 8E 45 9F 2E 74 63 39 99 B4 AC 3B 40 C8 9A EE B0 62 A8 E1 39 FE 8E 75 EC 28 6C 03 E6 3B 5F F5 6D 50 7D 1E 29 EC 3D 47 85 08 02 04 08 08 08 08 08 04 00 05 01 0E 12 AC F6 01 0E 00 56 00 01 00 52 13 80 42 00 00 02 02 00 00 18 AB 52 CF 5B E8 CD 95 CC 3F 5C A7 BA C9 C1 5D DD F8 E2 6E 0D A3 DF F8 76 00 20 D3 87 6B 1F F2 2B C7 53 38 60 F3 AD 07 82 8B F6 62 3C E0 DB 66 BC AD D0 68 D0 30 9D 8A 41 E7 75 00 0C 00 00 00 01 00 00 00 00 00 00 00 40 00 2F 00 2A 00 01 8F FE 4F BB B2 63 C7 69 C3 F1 3C DC A1 E8 77 A3 DD 97 FA 00 36 04 40 EF 11 7A 31 02 4E 10 13 94 02 28 00 00 00 00 00 00 01 0D 00 2C 00 01 00 28 EF CB 22 58 6F AE DC F5 CC CE 45 EE 6D CA E7 EF 06 3F 60 B5 8A 22 D5 9E 37 FA 92 9F A9 11 68 F0 2A 25 4A 45 C3 D4 56 CF 01 05 00 8A 00 01 02 02 00 41 01 00 01 03 3C 01 03 00 00 FB
56长度=39 89 04 81 64 6B C0 71 B5 6E B0 DF 7D D4 C0 7E 97 83 BC 9F 31 39 39 C3 95 93 D9 CD 48 00 1D 0D 18 52 87 21 B2 C1 B1 AD EF 96 82 D6 D4 57 EA 48 5A 27 8C 14 6F E2 83 00 56长度=39 89 04 81 64 6B C0 71 B5 6E B0 DF 7D D4 C0 7E 97 83 BC 9F 31 39 39 C3 95 93 D9 CD 48 00 1D 0D 18 52 87 21 B2 C1 B1 AD EF 96 82 D6 D4 57 EA 48 5A 27 8C 14 6F E2 83 00
Discarded(11) =41 01 00 02 03 3C 01 03 00 00 86 Discarded(11) =41 01 00 02 03 3C 01 03 00 00 86
*/ */
} }
else -> throw IllegalArgumentException(dataLength.toString()) else -> throw IllegalArgumentException(remaining.toString())
} }
//tlv0105 = "01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00" + 取文本中间(data, 取文本长度(data) - 367, 167) + “00 40 02 02 03 3C 01 03 00 00 ” + 取文本中间 (data, 取文本长度 (data) - 166, 167)
}
@AnnotatedId(KnownPacketId.SESSION_KEY)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionResponseDecryptionKey: IoBuffer): SessionKeyResponsePacket =
SessionKeyResponsePacket(decryptBy(sessionResponseDecryptionKey)).applySequence(sequenceId)
} }
} }
\ No newline at end of file
...@@ -5,53 +5,13 @@ package net.mamoe.mirai.network.protocol.tim.packet.login ...@@ -5,53 +5,13 @@ package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
/** object TouchKey : DecrypterByteArray, DecrypterType<TouchKey> {
* The packet received when logging in, used to redirect server address override val value: ByteArray = TIMProtocol.touchKey.hexToBytes(withCache = false)
*
* @see RedirectionPacket
* @see SubmitPasswordPacket
*
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@AnnotatedId(KnownPacketId.TOUCH)
class TouchResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
var serverIP: String? = null
var loginTime: Int = 0
lateinit var loginIP: String
lateinit var token0825: ByteArray//56
override fun decode() = with(input) {
when (val id = readByte().toUByte().toInt()) {
0xFE -> {
discardExact(94)
serverIP = readIP()
}
0x00 -> {
discardExact(4)
token0825 = readBytes(56)
discardExact(6)
loginTime = readInt()
loginIP = readIP()
}
else -> {
throw IllegalStateException(id.toByte().toUHexString())
}
}
}
@AnnotatedId(KnownPacketId.TOUCH)
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): TouchResponsePacket =
TouchResponsePacket(decryptBy(TIMProtocol.touchKey.hexToBytes())).applySequence(sequenceId)
}
} }
/** /**
...@@ -60,11 +20,12 @@ class TouchResponsePacket(input: ByteReadPacket) : ServerPacket(input) { ...@@ -60,11 +20,12 @@ class TouchResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
* @author Him188moe * @author Him188moe
*/ */
@AnnotatedId(KnownPacketId.TOUCH) @AnnotatedId(KnownPacketId.TOUCH)
object TouchPacket : OutgoingPacketBuilder { object TouchPacket : PacketFactory<TouchPacket.TouchResponse, TouchKey>(TouchKey) {
operator fun invoke( operator fun invoke(
bot: UInt, bot: UInt,
serverIp: String serverIp: String,
) = buildOutgoingPacket { isRedirect: Boolean
): OutgoingPacket = buildOutgoingPacket {
writeQQ(bot) writeQQ(bot)
writeHex(TIMProtocol.fixVer) writeHex(TIMProtocol.fixVer)
writeHex(TIMProtocol.touchKey) writeHex(TIMProtocol.touchKey)
...@@ -73,37 +34,44 @@ object TouchPacket : OutgoingPacketBuilder { ...@@ -73,37 +34,44 @@ object TouchPacket : OutgoingPacketBuilder {
writeHex(TIMProtocol.constantData1) writeHex(TIMProtocol.constantData1)
writeHex(TIMProtocol.constantData2) writeHex(TIMProtocol.constantData2)
writeQQ(bot) writeQQ(bot)
writeHex("00 00 00 00 03 09 00 08 00 01") writeHex(if (isRedirect) "00 01 00 00 03 09 00 0C 00 01" else "00 00 00 00 03 09 00 08 00 01")
writeIP(serverIp) writeIP(serverIp)
writeHex("00 02 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 02 00 19") writeHex(
if (isRedirect) "01 6F A1 58 22 01 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 03 00 19"
else "00 02 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 02 00 19"
)
writeHex(TIMProtocol.publicKey) writeHex(TIMProtocol.publicKey)
} }
} }
}
/** class TouchResponse : Packet {
* Server redirection (0825 response) var serverIP: String? = null
* internal set
* @author Him188moe var loginTime: Int = 0
*/ internal set
@AnnotatedId(KnownPacketId.TOUCH)
object RedirectionPacket : OutgoingPacketBuilder {
operator fun invoke(
bot: UInt,
serverIP: String
) = buildOutgoingPacket {
writeQQ(bot)
writeHex(TIMProtocol.fixVer)
writeHex(TIMProtocol.touchKey)//redirection key
encryptAndWrite(TIMProtocol.touchKey) { lateinit var loginIP: String
writeHex(TIMProtocol.constantData1) internal set
writeHex(TIMProtocol.constantData2) lateinit var token0825: ByteArray
writeQQ(bot) internal set
writeHex("00 01 00 00 03 09 00 0C 00 01") }
writeIP(serverIP)
writeHex("01 6F A1 58 22 01 00 36 00 12 00 02 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 14 00 1D 01 03 00 19") override suspend fun ByteReadPacket.decode(id: PacketId, sequenceId: UShort, handler: BotNetworkHandler<*>): TouchResponse = TouchResponse().apply {
writeHex(TIMProtocol.publicKey) when (val id = readByte().toUByte().toInt()) {
0xFE -> {
discardExact(94)
serverIP = readIP()
}
0x00 -> {
discardExact(4)
token0825 = readBytes(56)
discardExact(6)
loginTime = readInt()
loginIP = readIP()
}
else -> throw IllegalStateException(id.toByte().toUHexString())
} }
} }
} }
\ No newline at end of file
...@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils ...@@ -2,7 +2,6 @@ package net.mamoe.mirai.utils
import com.soywiz.klock.TimeSpan import com.soywiz.klock.TimeSpan
import com.soywiz.klock.seconds import com.soywiz.klock.seconds
import net.mamoe.mirai.network.protocol.tim.packet.login.TouchResponsePacket
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
/** /**
...@@ -10,7 +9,7 @@ import kotlin.jvm.JvmField ...@@ -10,7 +9,7 @@ import kotlin.jvm.JvmField
*/ */
class BotNetworkConfiguration { class BotNetworkConfiguration {
/** /**
* 等待 [TouchResponsePacket] 的时间 * 等待 [TouchRespnose] 的时间
*/ */
var touchTimeout: TimeSpan = 2.seconds var touchTimeout: TimeSpan = 2.seconds
/** /**
......
...@@ -21,7 +21,7 @@ fun ExternalImage( ...@@ -21,7 +21,7 @@ fun ExternalImage(
md5: ByteArray, md5: ByteArray,
format: String, format: String,
data: ByteReadPacket data: ByteReadPacket
) = ExternalImage(width, height, md5, format, data, data.remaining) ): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining)
/** /**
* 外部图片. 图片数据还没有读取到内存. * 外部图片. 图片数据还没有读取到内存.
......
...@@ -19,11 +19,20 @@ enum class OnlineStatus( ...@@ -19,11 +19,20 @@ enum class OnlineStatus(
/** /**
* 忙碌 * 忙碌
*/ */
BUSY(0x32u); BUSY(0x32u),
/**
* 离线 ? 也可能是被删好友 TODO confirm that
*/
OFFLINE(0x02u),
// TODO: 2019/10/29 what is 0x20u /**
* ?
*/
UNKNOWN(0x20u)
;
// TODO: 2019/10/29 what is 0x20u
companion object { companion object {
fun ofId(id: UByte): OnlineStatus? = values().firstOrNull { it.id == id } fun ofId(id: UByte): OnlineStatus? = values().firstOrNull { it.id == id }
} }
......
...@@ -3,6 +3,7 @@ package net.mamoe.mirai.utils ...@@ -3,6 +3,7 @@ package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer import kotlinx.io.core.IoBuffer
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.network.protocol.tim.packet.DecrypterByteArray
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import kotlin.experimental.and import kotlin.experimental.and
import kotlin.experimental.xor import kotlin.experimental.xor
...@@ -26,6 +27,8 @@ class DecryptionFailedException : Exception() ...@@ -26,6 +27,8 @@ class DecryptionFailedException : Exception()
*/ */
fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length) fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key, sourceLength = length)
fun ByteArray.encryptBy(key: DecrypterByteArray, length: Int = this.size): ByteArray = TEA.encrypt(this, key.value, sourceLength = length)
/** /**
* 通过 [String.hexToBytes] 将 [keyHex] 转为 [ByteArray] 后用它解密 [this]. * 通过 [String.hexToBytes] 将 [keyHex] 转为 [ByteArray] 后用它解密 [this].
* 将会使用 [HexCache] * 将会使用 [HexCache]
......
...@@ -3,14 +3,6 @@ ...@@ -3,14 +3,6 @@
package net.mamoe.mirai.utils.io package net.mamoe.mirai.utils.io
import kotlinx.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.KnownPacketId.*
import net.mamoe.mirai.network.protocol.tim.packet.action.*
import net.mamoe.mirai.network.protocol.tim.packet.event.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.decryptBy
fun ByteReadPacket.readRemainingBytes( fun ByteReadPacket.readRemainingBytes(
...@@ -23,66 +15,6 @@ fun ByteReadPacket.readIoBuffer( ...@@ -23,66 +15,6 @@ fun ByteReadPacket.readIoBuffer(
fun ByteReadPacket.readIoBuffer(n: Number) = this.readIoBuffer(n.toInt()) fun ByteReadPacket.readIoBuffer(n: Number) = this.readIoBuffer(n.toInt())
//必须消耗完 packet
fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {
discardExact(3)
val id = readUShort()
val sequenceId = readUShort()
discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8
return when (PacketId(id)) {
TOUCH -> TouchResponsePacket.Encrypted(this)
LOGIN ->
//todo 不要用size分析
when {
size == 271 || size == 207 -> LoginResponseKeyExchangeResponsePacket.Encrypted(this)
size == 871 -> LoginResponseCaptchaInitPacket.Encrypted(this)
size > 700 -> LoginResponseSuccessPacket.Encrypted(this)
else -> LoginResponseFailedPacket(
when (size) {
135 -> {//包数据错误. 目前怀疑是 tlv0006
this.readRemainingBytes().cutTail(1).decryptBy(TIMProtocol.shareKey).read {
discardExact(51)
MiraiLogger.error("Internal error: " + readUShortLVString())//抱歉,请重新输入密码。
}
LoginResult.INTERNAL_ERROR
}
319, 351 -> LoginResult.WRONG_PASSWORD
//135 -> LoginState.RETYPE_PASSWORD
63 -> LoginResult.BLOCKED
263 -> LoginResult.UNKNOWN_QQ_NUMBER
279, 495, 551, 487 -> LoginResult.DEVICE_LOCK
343, 359 -> LoginResult.TAKEN_BACK
else -> LoginResult.UNKNOWN
}, this)
}
SESSION_KEY -> SessionKeyResponsePacket.Encrypted(this)
CHANGE_ONLINE_STATUS -> ServerLoginSuccessPacket(this)
CAPTCHA -> ServerCaptchaPacket.Encrypted(this)
SERVER_EVENT_1, SERVER_EVENT_2 -> ServerEventPacket.Raw.Encrypted(this, PacketId(id), sequenceId)
FRIEND_ONLINE_STATUS_CHANGE -> FriendOnlineStatusChangedPacket.Encrypted(this)
S_KEY -> ResponsePacket.Encrypted<RequestSKeyPacket.Response>(this)
ACCOUNT_INFO -> ResponsePacket.Encrypted<RequestAccountInfoPacket.Response>(this)
SEND_GROUP_MESSAGE -> ResponsePacket.Encrypted<SendGroupMessagePacket.Response>(this)
SEND_FRIEND_MESSAGE -> ResponsePacket.Encrypted<SendFriendMessagePacket.Response>(this)
CAN_ADD_FRIEND -> ResponsePacket.Encrypted<CanAddFriendPacket.Response>(this)
HEARTBEAT -> ResponsePacket.Encrypted<HeartbeatPacket.Response>(this)
GROUP_IMAGE_ID -> ResponsePacket.Encrypted<GroupImageIdRequestPacket.Response>(this)
FRIEND_IMAGE_ID -> ResponsePacket.Encrypted<FriendImageIdRequestPacket.Response>(this)
REQUEST_PROFILE_DETAILS -> ResponsePacket.Encrypted<RequestProfileDetailsPacket.Response>(this)
// 0x01_BDu -> EventResponse.Encrypted<SubmitImageFilenamePacket.Response>(this)
else -> UnknownServerPacket.Encrypted(this, PacketId(id), sequenceId)
}.applySequence(sequenceId)
}
fun Input.readIP(): String = buildString(4 + 3) { fun Input.readIP(): String = buildString(4 + 3) {
repeat(4) { repeat(4) {
val byte = readUByte() val byte = readUByte()
......
...@@ -7,6 +7,8 @@ import kotlinx.io.pool.useInstance ...@@ -7,6 +7,8 @@ import kotlinx.io.pool.useInstance
import net.mamoe.mirai.contact.GroupId import net.mamoe.mirai.contact.GroupId
import net.mamoe.mirai.contact.GroupInternalId import net.mamoe.mirai.contact.GroupInternalId
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.DecrypterByteArray
import net.mamoe.mirai.network.protocol.tim.packet.login.PrivateKey
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.coerceAtMostOrFail import net.mamoe.mirai.utils.internal.coerceAtMostOrFail
import kotlin.random.Random import kotlin.random.Random
...@@ -20,6 +22,7 @@ fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt()) ...@@ -20,6 +22,7 @@ fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt())
fun BytePacketBuilder.writeQQ(qq: UInt) = this.writeUInt(qq) fun BytePacketBuilder.writeQQ(qq: UInt) = this.writeUInt(qq)
fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value) fun BytePacketBuilder.writeGroup(groupId: GroupId) = this.writeUInt(groupId.value)
fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value) fun BytePacketBuilder.writeGroup(groupInternalId: GroupInternalId) = this.writeUInt(groupInternalId.value)
fun BytePacketBuilder.writeFully(value: DecrypterByteArray) = this.writeFully(value.value)
fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) { fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size.toShort()) this.writeShort(byteArray.size.toShort())
...@@ -109,9 +112,11 @@ fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder. ...@@ -109,9 +112,11 @@ fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.
encryptAndWrite(it, encoder) encryptAndWrite(it, encoder)
} }
fun BytePacketBuilder.encryptAndWrite(key: DecrypterByteArray, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.value, encoder)
fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder) fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) { fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, loginIP: String, privateKey: PrivateKey) {
val firstMD5 = md5(password) val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray()) val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
...@@ -130,7 +135,7 @@ fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, l ...@@ -130,7 +135,7 @@ fun BytePacketBuilder.writeTLV0006(qq: UInt, password: String, loginTime: Int, l
writeZero(8) writeZero(8)
writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾 writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16 writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
writeFully(privateKey) writeFully(privateKey.value)
} }
} }
......
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asCoroutineDispatcher
import java.util.concurrent.Executors
internal actual val EventDispatcher: CoroutineDispatcher get() = Dispatchers.Default internal actual val EventDispatcher: CoroutineDispatcher = Executors.newFixedThreadPool(2).asCoroutineDispatcher()
\ No newline at end of file \ No newline at end of file
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import io.ktor.util.KtorExperimentalAPI
import io.ktor.util.cio.writeChannel import io.ktor.util.cio.writeChannel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
...@@ -19,14 +20,15 @@ import kotlin.math.min ...@@ -19,14 +20,15 @@ import kotlin.math.min
* *
* @return 用户输入得到的验证码 * @return 用户输入得到的验证码
*/ */
@KtorExperimentalAPI
internal actual suspend fun solveCaptcha(captchaBuffer: IoBuffer): String? = captchaLock.withLock { internal actual suspend fun solveCaptcha(captchaBuffer: IoBuffer): String? = captchaLock.withLock {
val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() } val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() }
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
tempFile.createNewFile() tempFile.createNewFile()
@Suppress("EXPERIMENTAL_API_USAGE") @Suppress("EXPERIMENTAL_API_USAGE")
MiraiLogger.info("需要验证码登录, 验证码为 4 字母")
try { try {
tempFile.writeChannel().writeFully(captchaBuffer) tempFile.writeChannel().writeFully(captchaBuffer)
MiraiLogger.info("需要验证码登录, 验证码为 4 字母")
MiraiLogger.info("若看不清字符图片, 请查看 ${tempFile.absolutePath}") MiraiLogger.info("若看不清字符图片, 请查看 ${tempFile.absolutePath}")
} catch (e: Exception) { } catch (e: Exception) {
MiraiLogger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") MiraiLogger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片")
......
...@@ -2,12 +2,11 @@ apply plugin: "kotlin" ...@@ -2,12 +2,11 @@ apply plugin: "kotlin"
apply plugin: "java" apply plugin: "java"
dependencies { dependencies {
implementation project(":mirai-core") api project(":mirai-core")
runtime files("../../mirai-core/build/classes/kotlin/jvm/main") // mpp targeting android limitation
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
compile group: 'com.alibaba', name: 'fastjson', version: '1.2.62' compile group: 'com.alibaba', name: 'fastjson', version: '1.2.62'
implementation 'org.jsoup:jsoup:1.12.1' implementation 'org.jsoup:jsoup:1.12.1'
implementation files('./lib/ExImageGallery.jar')
} }
...@@ -33,8 +33,8 @@ private fun readTestAccount(): BotAccount? { ...@@ -33,8 +33,8 @@ private fun readTestAccount(): BotAccount? {
suspend fun main() { suspend fun main() {
val bot = Bot( val bot = Bot(
readTestAccount() ?: BotAccount( readTestAccount() ?: BotAccount(
id = 1994701121u, id = 913366033u,
password = "123456" password = "a18260132383"
) )
).apply { login().requireSuccess() } ).apply { login().requireSuccess() }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment