Commit d89f8acc authored by Him188's avatar Him188

Multiplatform

parent c09d44f4
......@@ -4,6 +4,7 @@ ext {
// kotlin
kotlinJvm = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
kotlinCommon = "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
kotlinNative = "org.jetbrains.kotlin:kotlin-stdlib-native:$kotlin_version"
// coroutine
coroutine_version = "1.3.0"
......@@ -13,6 +14,8 @@ ext {
coroutineAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
coroutineJs = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutine_version"
coroutineIo = "org.jetbrains.kotlinx:kotlinx-coroutines-io:0.24.0"
// reflect
reflect = "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
......@@ -25,5 +28,6 @@ ext {
kotlinxIOJvm = "org.jetbrains.kotlinx:kotlinx-io-jvm:$kotlinx_io_version"
kotlinxIOCommon = "org.jetbrains.kotlinx:kotlinx-io:$kotlinx_io_version"
kotlinxIOJS = "org.jetbrains.kotlinx:kotlinx-io-js:$kotlinx_io_version"
kotlinxIONative = "org.jetbrains.kotlinx:kotlinx-io-native:$kotlinx_io_version"
}
package net.mamoe.mirai
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.config.MiraiConfig
import net.mamoe.mirai.utils.setting.MiraiSettings
import java.io.File
import java.io.IOException
import java.util.concurrent.ExecutionException
/**
* @author Him188moe
*/
/**
* Mirai 服务器.
......@@ -40,11 +39,11 @@ object MiraiServer {
this.logger = MiraiLogger
logger.info("About to run Mirai (" + Mirai.VERSION + ") under " + if (isUnix) "unix" else "windows")
logger.info("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder)
logger.logInfo("About to run Mirai (" + Mirai.VERSION + ") under " + if (isUnix) "unix" else "windows")
logger.logInfo("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder)
val setting = this.parentFolder + "/Mirai.ini"
logger.info("Selecting setting from " + LoggerTextFormat.GREEN + setting)
val setting = File(this.parentFolder, "/Mirai.ini")
logger.logInfo("Selecting setting from " + LoggerTextFormat.GREEN + setting)
/*
if (!setting.exists()) {
......@@ -54,7 +53,7 @@ object MiraiServer {
}
File qqs = new File(this.parentFolder + "/QQ.yml");
getLogger().info("Reading QQ accounts from " + LoggerTextFormat.GREEN + qqs);
getLogger().logInfo("Reading QQ accounts from " + LoggerTextFormat.GREEN + qqs);
if (!qqs.exists()) {
this.initQQConfig(qqs);
} else {
......@@ -67,10 +66,10 @@ object MiraiServer {
/*
MiraiSettingMapSection qqs = this.setting.getMapSection("qq");
qqs.forEach((a,p) -> {
this.getLogger().info("Finding available ports between " + "1-65536");
this.getLogger().logInfo("Finding available ports between " + "1-65536");
try {
int port = MiraiNetwork.getAvailablePort();
this.getLogger().info("Listening on port " + port);
this.getLogger().logInfo("Listening on port " + port);
} catch (IOException e) {
e.printStackTrace();
......@@ -84,18 +83,18 @@ object MiraiServer {
fun shutdown() {
if (this.enabled) {
logger.info("About to shutdown Mirai")
logger.info("Data have been saved")
logger.logInfo("About to shutdown Mirai")
logger.logInfo("Data have been saved")
}
}
private fun initSetting(setting: File) {
logger.info("Thanks for using Mirai")
logger.info("initializing Settings")
logger.logInfo("Thanks for using Mirai")
logger.logInfo("initializing Settings")
try {
if (setting.createNewFile()) {
logger.info("Mirai Config Created")
logger.logInfo("Mirai Config Created")
}
} catch (e: IOException) {
e.printStackTrace()
......@@ -113,24 +112,24 @@ object MiraiServer {
worker["core_task_pool_worker_amount"] = 5
val plugin = this.settings.getMapSection("plugin")
plugin["debug"] = false
plugin["logDebug"] = false
this.settings.save()
logger.info("initialized; changing can be made in setting file: $setting")
logger.logInfo("initialized; changing can be made in setting file: $setting")
}
private fun initQQConfig(qqConfig: File) {
this.qqs = MiraiConfig(qqConfig)
MiraiLogger.info("QQ account initialized; changing can be made in Config file: $qqConfig")
logger.info("QQ 账户管理初始化完毕")
MiraiLogger.logInfo("QQ account initialized; changing can be made in Config file: $qqConfig")
logger.logInfo("QQ 账户管理初始化完毕")
}
private fun reload() {
this.enabled = true
MiraiLogger.info(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai")
MiraiLogger.info("Mirai Version=" + Mirai.VERSION)
MiraiLogger.logInfo(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai")
MiraiLogger.logInfo("Mirai Version=" + Mirai.VERSION)
MiraiLogger.info("Initializing [Bot]s")
MiraiLogger.logInfo("Initializing [Bot]s")
try {
availableBot
......@@ -142,23 +141,23 @@ object MiraiServer {
/*
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
getLogger().info("Initializing [Bot] " + section.getString("account"));
getLogger().logInfo("Initializing [Bot] " + section.getString("account"));
try {
Bot bot = new Bot(section);
var state = bot.network.login$mirai_core().of();
//bot.network.login$mirai_core().whenComplete((state, e) -> {
if (state == LoginState.SUCCESS) {
Bot.instances.add(bot);
getLogger().green(" Login Succeed");
getLogger().logGreen(" Login Succeed");
} else {
getLogger().error(" Login Failed with error " + state);
getLogger().logError(" Login Failed with logError " + state);
bot.close();
}
// }).of();
} catch (Throwable e) {
e.printStackTrace();
getLogger().error("Could not load QQ bots config!");
getLogger().logError("Could not load QQ bots config!");
System.exit(1);
}
});*/
......@@ -173,9 +172,9 @@ object MiraiServer {
get() {
for (it in qqList.split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()) {
val strings = it.split("----").dropLastWhile { it.isEmpty() }.toTypedArray()
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), Console())
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), MiraiLogger)
if (runBlocking { bot.login() } === LoginState.SUCCESS) {
if (runBlocking { bot.login() } === LoginResult.SUCCESS) {
bot.green("Login succeed")
return bot
}
......
......@@ -4,22 +4,40 @@ apply plugin: "kotlin-multiplatform"
kotlin {
targets {
fromPreset(presets.jvm, "jvm")
//fromPreset(presets.mingwX64, "mingwX64")
}
jvm()
/*
mingwX64("mingwX64") {
binaries {
executable {
// Change to specify fully qualified name of your application's entry point:
entryPoint = 'main'
// Specify command-line arguments, if necessary:
runTask?.args('')
}
}
}*/
sourceSets {
commonMain {
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation rootProject.ext.kotlinCommon
implementation rootProject.ext.reflect
implementation rootProject.ext.coroutine
implementation rootProject.ext.kotlinJvm
//implementation rootProject.ext.coroutine
implementation rootProject.ext.coroutineCommon
implementation rootProject.ext.coroutineIo
implementation rootProject.ext.atomicFUCommon
implementation rootProject.ext.kotlinxIOCommon
}
}
jvmMain {
apply plugin: 'java'
......@@ -35,6 +53,23 @@ kotlin {
implementation 'org.ini4j:ini4j:0.5.2'
}
}
/*
mingwX64Main {
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation rootProject.ext.kotlinCommon
implementation rootProject.ext.coroutine
implementation rootProject.ext.coroutineNative
implementation rootProject.ext.kotlinNative
implementation rootProject.ext.reflect
//implementation rootProject.ext.coroutine
implementation rootProject.ext.kotlinxIONative
}
}*/
jvmTest {
}
......@@ -47,16 +82,3 @@ kotlin {
compileKotlinJvm {
}
\ No newline at end of file
/*
dependencies {
compile 'com.google.protobuf:protobuf-java:3.5.0'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2'
compile 'io.netty:netty-all:4.1.38.Final'
compile 'net.java.dev.jna:jna:5.4.0'
compile 'org.apache.logging.log4j:log4j-core:2.12.1'
compile 'org.yaml:snakeyaml:1.18'
compile 'org.jetbrains.kotlin:kotlin-reflect:1.3.41'
compile 'org.jsoup:jsoup:1.12.1'
compile 'org.ini4j:ini4j:0.5.2'
}*/
package net.mamoe.mirai
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot.ContactSystem
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
......@@ -12,12 +14,12 @@ import net.mamoe.mirai.utils.MiraiLogger
/**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人.
* <br></br>
*
* [Bot] 由 3 个模块组成.
* [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问
* [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问
* [机器人账号信息][BotAccount]: 可通过 [Bot.account] 访问
* <br></br>
*
* 若你需要得到机器人的 QQ 账号, 请访问 [Bot.account]
* 若你需要得到服务器上所有机器人列表, 请访问 [Bot.instances]
*
......@@ -28,7 +30,7 @@ import net.mamoe.mirai.utils.MiraiLogger
* a [ContactSystem], which manage contacts such as [QQ] and [Group];
* a [TIMBotNetworkHandler], which manages the connection to the server;
* a [BotAccount], which stores the account information(e.g. qq number the bot)
* <br></br>
*
* To of all the QQ contacts, access [Bot.account]
* To of all the Robot instance, access [Bot.instances]
*
......@@ -42,7 +44,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
val contacts = ContactSystem()
val network: BotNetworkHandler = TIMBotNetworkHandler(this)
val network: BotNetworkHandler<*> = TIMBotNetworkHandler(this)
init {
instances.add(this)
......@@ -59,25 +61,22 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
*/
inner class ContactSystem internal constructor() {
val groups = ContactList<Group>()
private val groupsLock = Mutex()
val qqs = ContactList<QQ>()
private val qqsLock = Mutex()
fun getQQ(qqNumber: Long): QQ {
synchronized(this.qqs) {
if (!this.qqs.containsKey(qqNumber)) {
this.qqs[qqNumber] = QQ(this@Bot, qqNumber)
}
return this.qqs[qqNumber]!!
}
}
/**
* 通过群号码获取群对象.
* 注意: 在并发调用时, 这个方法并不是原子的.
*/
fun getQQ(qqNumber: Long): QQ = qqs.getOrPut(qqNumber) { QQ(this@Bot, qqNumber) }
/**
* 通过群号码获取群对象.
* 注意: 在并发调用时, 这个方法并不是原子的.
*/
fun getGroupByNumber(groupNumber: Long): Group = groups.getOrPut(groupNumber) { Group(this@Bot, groupNumber) }
fun getGroupByNumber(groupNumber: Long): Group {
synchronized(this.groups) {
if (!this.groups.containsKey(groupNumber)) {
this.groups[groupNumber] = Group(this@Bot, groupNumber)
}
return this.groups[groupNumber]!!
}
}
fun getGroupById(groupId: Long): Group {
return getGroupByNumber(Group.groupIdToNumber(groupId))
......@@ -86,7 +85,6 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
fun close() {
this.network.close()
this.contacts.groups.values.forEach { it.close() }
this.contacts.groups.clear()
this.contacts.qqs.clear()
}
......@@ -94,8 +92,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
companion object {
val instances: MutableList<Bot> = mutableListOf()
private var id = 0
private val idLock = Any()
fun nextId(): Int = synchronized(idLock) { id++ }
private val id = atomic(0)
fun nextId(): Int = id.addAndGet(1)
}
}
\ No newline at end of file
@file:Suppress("unused")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
import kotlinx.io.core.readBytes
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.goto
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.ContactList
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.toUHexString
import java.text.SimpleDateFormat
import java.util.*
/**
* The mirror of functions in inner classes of [Bot]
......@@ -23,7 +21,10 @@ import java.util.*
//Contacts
fun Bot.getQQ(number: Long): QQ = this.contacts.getQQ(number)
fun Bot.getQQ(number: UInt): QQ = getQQ(number.toLong())
fun Bot.getGroupByNumber(number: Long): Group = this.contacts.getGroupByNumber(number)
fun Bot.getGroupByNumber(number: UInt): Group = getGroupByNumber(number.toLong())
fun Bot.getGroupById(number: Long): Group = this.contacts.getGroupById(number)
......@@ -33,9 +34,11 @@ val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
//NetworkHandler
suspend fun Bot.sendPacket(packet: ClientPacket) = this.network.socket.sendPacket(packet)
suspend fun Bot.sendPacket(packet: ClientPacket) = this.network.sendPacket(packet)
suspend fun Bot.login(configuration: LoginConfiguration.() -> Unit): LoginResult = this.network.login(LoginConfiguration().apply(configuration))
suspend fun Bot.login(): LoginState = this.network.login()
suspend fun Bot.login(): LoginResult = this.network.login(LoginConfiguration.Default)
//BotAccount
val Bot.qqNumber: Long get() = this.account.qqNumber
......@@ -45,31 +48,19 @@ val Bot.qqNumber: Long get() = this.account.qqNumber
fun Bot.log(o: Any?) = info(o)
fun Bot.println(o: Any?) = info(o)
fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET)
fun Bot.info(o: Any?) = this.logger.logInfo(o)
fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED)
fun Bot.error(o: Any?) = this.logger.logError(o)
fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
fun Bot.purple(o: Any?) = this.logger.logPurple(o)
fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
fun Bot.cyan(o: Any?) = this.logger.logCyan(o)
fun Bot.green(o: Any?) = this.logger.logGreen(o)
fun Bot.cyan(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
fun Bot.green(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
fun Bot.debug(o: Any?) = this.logger.logDebug(o)
fun Bot.printPacketDebugging(packet: ServerPacket) {
debug("Packet=$packet")
debug("Packet size=" + packet.input.goto(0).readAllBytes().size)
debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString())
}
private fun print(bot: Bot, value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
println("$color[Mirai] $s #R${bot.id}: $value")
}
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
println("$color[Mirai] $s : $value")
debug("Packet size=" + packet.input.readBytes().size)
debug("Packet data=" + packet.input.readBytes().toUHexString())
}
\ No newline at end of file
package net.mamoe.mirai
//expect fun s(): String
/**
* @author Him188moe
*/
object Mirai {
const val VERSION: String = "1.0.0"
}
\ No newline at end of file
......@@ -4,7 +4,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group.Companion.groupNumberToId
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.utils.ContactList
import java.io.Closeable
import kotlin.jvm.JvmStatic
/**
* 群.
......@@ -21,22 +21,20 @@ import java.io.Closeable
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @author Him188moe
*/
class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
class Group(bot: Bot, number: Long) : Contact(bot, number) {
val groupId = groupNumberToId(number)
val members = ContactList<QQ>()//todo members
val members: ContactList<QQ>
//todo members
get() = throw UnsupportedOperationException("Not yet supported")
override suspend fun sendMessage(message: MessageChain) {
bot.network.message.sendGroupMessage(this, message)
bot.network.event.sendGroupMessage(this, message)
}
override suspend fun sendXMLMessage(message: String) {
}
override fun close() {
this.members.clear()
}
companion object {
@JvmStatic
fun groupNumberToId(number: Long): Long {//求你别出错
......
......@@ -12,34 +12,26 @@ import net.mamoe.mirai.message.MessageChain
* Java 获取 qq 号: `qq.getNumber()`
* Java 获取所属 bot: `qq.getBot()`
*
* A QQ instance helps you to receive message from or sendPacket message to.
* A QQ instance helps you to receive event from or sendPacket event to.
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
*
* @author Him188moe
*/
class QQ(bot: Bot, number: Long) : Contact(bot, number) {
override suspend fun sendMessage(message: MessageChain) {
bot.network.message.sendFriendMessage(this, message)
bot.network.event.sendFriendMessage(this, message)
}
override suspend fun sendXMLMessage(message: String) {
}
}
/**
/**
* At(@) this account.
*
* @return an instance of [Message].
*/
fun at(): At {
fun QQ.at(): At {
return At(this)
}
/*
Make that we can use (QQ + QQ2 + QQ3).sendMessage( )
operator fun plus(qq: QQ): QQCombination {
}*/
}
\ No newline at end of file
......@@ -2,10 +2,16 @@
package net.mamoe.mirai.event
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.event.internal.broadcastInternal
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.log
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmOverloads
/**
* 所有事件的基类.
......@@ -13,7 +19,6 @@ import net.mamoe.mirai.event.internal.broadcastInternal
*
* @see [broadcast] 广播事件
* @see [subscribe] 监听事件
* @author Him188moe
*/
abstract class Event {
......@@ -31,7 +36,6 @@ abstract class Event {
*
* @throws UnsupportedOperationException 如果事件没有实现 [Cancellable]
*/
@Throws(UnsupportedOperationException::class)
fun cancel() {
cancelled = true
}
......@@ -39,8 +43,6 @@ abstract class Event {
/**
* 实现这个接口的事件可以被取消.
*
* @author Him188moe
*/
interface Cancellable {
val cancelled: Boolean
......@@ -51,8 +53,23 @@ interface Cancellable {
/**
* 广播一个事件的唯一途径
*/
@Synchronized
@Suppress("UNCHECKED_CAST")
suspend fun <E : Event> E.broadcast(): E = withContext(EventScope.coroutineContext) { this@broadcast.broadcastInternal() }
@JvmOverloads
suspend fun <E : Event> E.broadcast(context: CoroutineContext? = null): E {
var ctx = EventScope.coroutineContext
if (context != null) {
if (context[CoroutineExceptionHandler] == null)
ctx += CoroutineExceptionHandler { _, e -> e.log() }
ctx += context
}
return withContext(ctx) { this@broadcast.broadcastInternal() }
}
/**
* 事件协程作用域.
* 所有的事件 [broadcast] 过程均在此作用域下运行.
*
* 然而, 若在事件处理过程中使用到 [Contact.sendMessage] 等会 [发送数据包][BotNetworkHandler.sendPacket] 的方法,
* 发送过程将会通过 [withContext] 将协程切换到 [BotNetworkHandler.NetworkScope]
*/
object EventScope : CoroutineScope by CoroutineScope(Dispatchers.Default)//todo may change
\ No newline at end of file
......@@ -5,6 +5,7 @@ package net.mamoe.mirai.event
import net.mamoe.mirai.event.internal.Handler
import net.mamoe.mirai.event.internal.listeners
import net.mamoe.mirai.event.internal.subscribeInternal
import kotlin.jvm.Synchronized
import kotlin.reflect.KClass
enum class ListeningStatus {
......@@ -76,8 +77,8 @@ inline fun <reified E : Event> subscribeAll(noinline listeners: ListenerBuilder<
* }
*
* untilFalse {
* it.reply("你发送了 ${it.message}")
* it.message eq "停止"
* it.reply("你发送了 ${it.event}")
* it.event eq "停止"
* }
* }
* ```
......
......@@ -3,9 +3,7 @@ package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.Event
/**
* @author Him188moe
*/
abstract class BotEvent(val bot: Bot) : Event()
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
\ No newline at end of file
......@@ -5,9 +5,7 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
*/
abstract class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot)
/**
......@@ -16,15 +14,11 @@ abstract class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot)
* @author Him188moe
*/
class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : FriendEvent(bot, sender) {
@JvmSynthetic
suspend inline fun reply(message: Message) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: String) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: List<Message>) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)//shortcut
}
\ No newline at end of file
......@@ -6,24 +6,16 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
*/
abstract class GroupEvent(bot: Bot, val group: Group) : BotEvent(bot)
/**
* @author Him188moe
*/
class GroupMessageEvent(bot: Bot, group: Group, val sender: QQ, val message: MessageChain) : GroupEvent(bot, group) {
@JvmSynthetic
suspend inline fun reply(message: Message) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: String) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: List<Message>) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: MessageChain) = group.sendMessage(message)
}
\ No newline at end of file
......@@ -8,9 +8,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
/* Abstract */
/**
* @author Him188moe
*/
sealed class PacketEvent<out P : Packet>(bot: Bot, open val packet: P) : BotEvent(bot)
......
package net.mamoe.mirai.event.internal
import kotlinx.coroutines.delay
import net.mamoe.mirai.Bot
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.dataInputStream
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.isSuperclassOf
/**
* 监听和广播实现
......@@ -42,14 +33,16 @@ class Handler<E : Event>(val handler: suspend (E) -> ListeningStatus) : Listener
internal val <E : Event> KClass<E>.listeners: EventListeners<E> get() = EventListenerManger.get(this)
internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf()
internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf() {
val lock = Mutex()
}
internal object EventListenerManger {
private val registries: MutableMap<KClass<out Event>, EventListeners<out Event>> = mutableMapOf()
@Suppress("UNCHECKED_CAST")
internal fun <E : Event> get(clazz: KClass<E>): EventListeners<E> {
synchronized(clazz) {
//synchronized(clazz) {
if (registries.containsKey(clazz)) {
return registries[clazz] as EventListeners<E>
} else {
......@@ -58,13 +51,13 @@ internal object EventListenerManger {
return it
}
}
}
//}
}
}
@Suppress("UNCHECKED_CAST")
internal suspend fun <E : Event> E.broadcastInternal(): E {
suspend fun callListeners(listeners: EventListeners<in E>) {
suspend fun callListeners(listeners: EventListeners<in E>) = listeners.lock.withLock {
val iterator = listeners.iterator()
while (iterator.hasNext()) {
if (iterator.next().onEvent(this) == ListeningStatus.STOPPED) {
......@@ -74,24 +67,9 @@ internal suspend fun <E : Event> E.broadcastInternal(): E {
}
callListeners(this::class.listeners as EventListeners<in E>)
this::class.allSuperclasses.forEach {
//println("super: " + it.simpleName)
if (Event::class.isSuperclassOf(it)) {
callListeners((it as KClass<out Event>).listeners as EventListeners<in E>)
}
}
loopAllListeners(this::class) { callListeners(it as EventListeners<in E>) }
return this
}
suspend fun main() {
ServerPacketReceivedEvent::class.subscribeAlways {
println("got it")
}
println(ServerPacketReceivedEvent::class.listeners.size)
ServerPacketReceivedEvent(Bot(BotAccount(1, ""), Console()), object : ServerPacket(byteArrayOf().dataInputStream()) {}).broadcast()
delay(1000)
}
\ No newline at end of file
internal expect inline fun <E : Event> loopAllListeners(clazz: KClass<E>, consumer: (EventListeners<in E>) -> Unit)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.message
/**
* @author LamGC
*/
@Suppress("EnumEntryName", "unused", "SpellCheckingInspection")
enum class FaceID constructor(val id: UByte) {
unknown(0xffu),
// TODO: 2019/9/1 添加更多表情
jingya(0u),
piezui(1u),
se(2u),
fadai(3u),
deyi(4u),
liulei(5u),
haixiu(6u),
bizui(7u),
shui(8u),
daku(9u),
ganga(10u),
fanu(11u),
tiaopi(12u),
ciya(13u),
weixiao(14u),
nanguo(15u),
ku(16u),
zhuakuang(18u),
tu(19u),
touxiao(20u),
keai(21u),
baiyan(22u),
aoman(23u),
ji_e(24u),
kun(25u),
jingkong(26u),
liuhan(27u),
hanxiao(28u),
dabing(29u),
fendou(30u),
zhouma(31u),
yiwen(32u),
yun(34u),
zhemo(35u),
shuai(36u),
kulou(37u),
qiaoda(38u),
zaijian(39u),
fadou(41u),
aiqing(42u),
tiaotiao(43u),
zhutou(46u),
yongbao(49u),
dan_gao(53u),
shandian(54u),
zhadan(55u),
dao(56u),
zuqiu(57u),
bianbian(59u),
kafei(60u),
fan(61u),
meigui(63u),
diaoxie(64u),
aixin(66u),
xinsui(67u),
liwu(69u),
taiyang(74u),
yueliang(75u),
qiang(76u),
ruo(77u),
woshou(78u),
shengli(79u),
feiwen(85u),
naohuo(86u),
xigua(89u),
lenghan(96u),
cahan(97u),
koubi(98u),
guzhang(99u),
qiudale(100u),
huaixiao(101u),
zuohengheng(102u),
youhengheng(103u),
haqian(104u),
bishi(105u),
weiqu(106u),
kuaikule(107u),
yinxian(108u),
qinqin(109u),
xia(110u),
kelian(111u),
caidao(112u),
pijiu(113u),
lanqiu(114u),
pingpang(115u),
shiai(116u),
piaochong(117u),
baoquan(118u),
gouyin(119u),
quantou(120u),
chajin(121u),
aini(122u),
bu(123u),
hao(124u),
zhuanquan(125u),
ketou(126u),
huitou(127u),
tiaosheng(128u),
huishou(129u),
jidong(130u),
jiewu(131u),
xianwen(132u),
zuotaiji(133u),
youtaiji(134u),
shuangxi(136u),
bianpao(137u),
denglong(138u),
facai(139u),
K_ge(140u),
gouwu(141u),
youjian(142u),
shuai_qi(143u),
hecai(144u),
qidao(145u),
baojin(146u),
bangbangtang(147u),
he_nai(148u),
xiamian(149u),
xiangjiao(150u),
feiji(151u),
kaiche(152u),
gaotiezuochetou(153u),
chexiang(154u),
gaotieyouchetou(155u),
duoyun(156u),
xiayu(157u),
chaopiao(158u),
xiongmao(159u),
dengpao(160u),
fengche(161u),
naozhong(162u),
dasan(163u),
caiqiu(164u),
zuanjie(165u),
shafa(166u),
zhijin(167u),
yao(168u),
shouqiang(169u),
qingwa(170u);
override fun toString(): String {
return "$name($id)"
}
companion object {
fun ofId(id: UByte): FaceID {
for (value in values()) {
if (value.id == id) {
return value
}
}
return unknown
}
}
}
......@@ -13,12 +13,12 @@ import net.mamoe.mirai.contact.QQ
* 这与使用 [String] 的使用非常类似.
*
* 比较 [Message] 与 [String] (使用 infix [Message.eq]):
* `if(message eq "你好") qq.sendMessage(message)`
* `if(event eq "你好") qq.sendMessage(event)`
*
* 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]):
* ```
* message = PlainText("Hello ")
* qq.sendMessage(message + "world")
* event = PlainText("Hello ")
* qq.sendMessage(event + "world")
* ```
*
* 但注意: 不能 `String + Message`. 只能 `Message + String`
......
......@@ -2,11 +2,9 @@
package net.mamoe.mirai.message
/**
* @author Him188moe
*/
@Suppress("unused")
enum class MessageType(private val value: UByte) {
enum class MessageType(val value: UByte) {
PLAIN_TEXT(0x03u),
AT(0x06u),
FACE(0x02u),
......
......@@ -10,7 +10,3 @@ fun String.toMessage(): PlainText = PlainText(this)
* 用 `this` 构造 [MessageChain]
*/
fun Message.toChain(): MessageChain = if (this is MessageChain) this else MessageChain(this)
\ No newline at end of file
fun MessageChain.containsType(clazz: Class<out Message>): Boolean = list.any { clazz.isInstance(it) }
operator fun MessageChain.contains(sub: Class<out Message>): Boolean = containsType(sub)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.message.internal
import kotlinx.io.core.*
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
import java.io.DataInputStream
internal fun ByteArray.parseMessageFace(): Face = dataDecode(this) {
internal fun ByteArray.parseMessageFace(): Face = read {
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
//00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D
it.skip(1)
discardExact(1)
val id1 = FaceID.ofId(it.readLVNumber().toInt())//可能这个是id, 也可能下面那个
it.skip(it.readByte().toLong())
it.readLVNumber()//某id?
return@dataDecode Face(id1)
val id1 = FaceID.ofId(readLVNumber().toInt().toUByte())//可能这个是id, 也可能下面那个
discardExact(readByte().toLong())
readLVNumber()//某id?
return@read Face(id1)
}
internal fun ByteArray.parsePlainText(): PlainText = dataDecode(this) {
it.skip(1)
PlainText(it.readLVString())
internal fun ByteArray.parsePlainText(): PlainText = read {
discardExact(1)
PlainText(readLVString())
}
internal fun ByteArray.parseMessageImage0x06(): Image = dataDecode(this) {
it.skip(1)
MiraiLogger.debug("好友的图片")
MiraiLogger.debug(this.toUHexString())
val filenameLength = it.readShort()
val suffix = it.readString(filenameLength).substringAfter(".")
it.skip(this.size - 37 - 1 - filenameLength - 2)
val imageId = String(it.readNBytes(36))
MiraiLogger.debug(imageId)
it.skip(1)//0x41
return@dataDecode Image("{$imageId}.$suffix")
internal fun ByteArray.parseMessageImage0x06(): Image = read {
discardExact(1)
MiraiLogger.logDebug("好友的图片")
MiraiLogger.logDebug(this@parseMessageImage0x06.toUHexString())
val filenameLength = readShort()
val suffix = readString(filenameLength).substringAfter(".")
discardExact(this@parseMessageImage0x06.size - 37 - 1 - filenameLength - 2)
val imageId = readString(36)
MiraiLogger.logDebug(imageId)
discardExact(1)//0x41
return@read Image("{$imageId}.$suffix")
}
internal fun ByteArray.parseMessageImage0x03(): Image = dataDecode(this) {
it.skip(1)
return@dataDecode Image(String(it.readLVByteArray()))
internal fun ByteArray.parseMessageImage0x03(): Image = read {
discardExact(1)
return@read Image(String(readLVByteArray()))
/*
println(String(it.readLVByteArray()))
it.readTLVMap()
return@dataDecode Image(String(it.readLVByteArray().cutTail(5).getRight(42)))
println(String(readLVByteArray()))
readTLVMap()
return Image(String(readLVByteArray().cutTail(5).getRight(42)))
/
it.skip(data.size - 47)
val imageId = String(it.readNBytes(42))
it.skip(1)//0x41
it.skip(1)//0x42
it.skip(1)//0x43
it.skip(1)//0x41
return@dataDecode Image(imageId)*/
discardExact(data.size - 47)
val imageId = String(readBytes(42))
discardExact(1)//0x41
discardExact(1)//0x42
discardExact(1)//0x43
discardExact(1)//0x41
return Image(imageId)*/
}
internal fun ByteArray.parseMessageChain(): MessageChain = dataDecode(this) {
it.readMessageChain()
internal fun ByteArray.parseMessageChain(): MessageChain = read {
readMessageChain()
}
internal fun DataInputStream.readMessage(): Message? {
internal fun ByteReadPacket.readMessage(): Message? {
val messageType = this.readByte().toInt()
val sectionLength = this.readShort().toLong()//sectionLength: short
val sectionData = this.readNBytes(sectionLength)
val sectionData = this.readBytes(sectionLength.toInt())//use buffer instead
return when (messageType) {
0x01 -> sectionData.parsePlainText()
0x02 -> sectionData.parseMessageFace()
......@@ -81,7 +82,7 @@ internal fun DataInputStream.readMessage(): Message? {
println(value.size)
println(value.toUHexString())
//todo 未知压缩算法
this.skip(7)//几个TLV
this.discardExact(7)//几个TLV
return PlainText(String(value))
}
......@@ -92,21 +93,21 @@ internal fun DataInputStream.readMessage(): Message? {
else -> {
println("未知的messageType=0x${messageType.toByte().toUHexString()}")
println("后文=${this.readAllBytes().toUHexString()}")
println("后文=${this.readBytes().toUHexString()}")
null
}
}
}
fun DataInputStream.readMessageChain(): MessageChain {
fun ByteReadPacket.readMessageChain(): MessageChain {
val chain = MessageChain()
var got: Message? = null
do {
if (got != null) {
chain.concat(got)
}
if (this.available() == 0) {
if (this.remaining == 0L) {
return chain
}
got = this.readMessage()
......@@ -114,34 +115,34 @@ fun DataInputStream.readMessageChain(): MessageChain {
return chain
}
fun MessageChain.toByteArray(): ByteArray = dataEncode { result ->
this@toByteArray.list.forEach { message ->
result.write(with(message) {
fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
this@toPacket.list.forEach { message ->
writePacket(with(message) {
when (this) {
is Face -> dataEncode { section ->
section.writeByte(MessageType.FACE.intValue)
is Face -> buildPacket {
writeUByte(MessageType.FACE.value)
section.writeLVByteArray(dataEncode { child ->
child.writeShort(1)
child.writeByte(this.id.id)
writeLVPacket {
writeShort(1)
writeUByte(id.id)
child.writeHex("0B 00 08 00 01 00 04 52 CC F5 D0 FF")
writeHex("0B 00 08 00 01 00 04 52 CC F5 D0 FF")
child.writeShort(2)
child.writeByte(0x14)//??
child.writeByte(this.id.id + 65)
})
writeShort(2)
writeByte(0x14)//??
writeUByte((id.id + 65u).toUByte())
}
}
is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported")
is Image -> dataEncode { section ->
section.writeByte(MessageType.IMAGE.intValue)
is Image -> buildPacket {
writeUByte(MessageType.IMAGE.value)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x02)
child.writeLVString(this.imageId)
child.writeHex("04 00 " +
writeLVPacket {
writeByte(0x02)
writeLVString(imageId)
writeHex("04 00 " +
"04 9B 53 B0 08 " +
"05 00 " +
"04 D9 8A 5A 70 " +
......@@ -149,19 +150,19 @@ fun MessageChain.toByteArray(): ByteArray = dataEncode { result ->
"04 00 00 00 50 " +
"07 00 " +
"01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 11 00 00 00 15 00 04 00 00 02 BC 16 00 04 00 00 02 BC 18 00 04 00 00 7D 5E FF 00 5C 15 36 20 39 32 6B 41 31 43 39 62 35 33 62 30 30 38 64 39 38 61 35 61 37 30 20")
child.writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
child.writeBytes(this.imageId)
child.writeByte(0x41)
})
writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
writeStringUtf8(imageId)
writeByte(0x41)
}
}
is PlainText -> dataEncode { section ->
section.writeByte(MessageType.PLAIN_TEXT.intValue)
is PlainText -> buildPacket {
writeUByte(MessageType.PLAIN_TEXT.value)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x01)
child.writeLVString(this.stringValue)
})
writeLVPacket {
writeByte(0x01)
writeLVString(stringValue)
}
}
else -> throw UnsupportedOperationException("${this::class.simpleName} is not supported")
......
package net.mamoe.mirai.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.io.core.Closeable
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocket
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket
import net.mamoe.mirai.network.protocol.tim.handler.MessagePacketHandler
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.MiraiDatagramChannel
/**
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
......@@ -20,42 +20,49 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
* - [BotSocket]: 处理数据包底层的发送([ByteArray])
* - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理
*
* 其中, [PacketHandler] 由 4 个子模块构成:
* - [DebugPacketHandler] 输出 [Packet.toString]
* - [LoginHandler] 处理 touch/login/verification code 相关
* - [MessagePacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* 其中, [PacketHandler] 由 3 个子模块构成:
* - [LoginHandler] 处理 sendTouch/login/verification code 相关
* - [EventPacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等)
*
* A BotNetworkHandler is used to connect with Tencent servers.
*
* @author Him188moe
*/
interface BotNetworkHandler {
interface BotNetworkHandler<Socket : DataPacketSocket> : Closeable {
/**
* 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray]
* [BotNetworkHandler] 的协程作用域.
* 所有 [BotNetworkHandler] 的协程均启动在此作用域下.
*
* [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [MiraiDatagramChannel.read]
* - 心跳 Job [ClientHeartbeatPacket]
* - SKey 刷新 [ClientSKeyRefreshmentRequestPacket]
* - 所有数据包处理和发送
*
* java 调用方式: `botNetWorkHandler.getSocket()`
* [BotNetworkHandler.close] 时将会 [取消][CoroutineScope.cancel] 所有此作用域下的协程
*/
val socket: DataPacketSocket
val NetworkScope: CoroutineScope
var socket: Socket
/**
* 消息处理. 如发送好友消息, 接受群消息并触发事件
*
* java 调用方式: `botNetWorkHandler.getMessage()`
* 事件处理. 如发送好友消息, 接受群消息并触发事件
*/
val message: MessagePacketHandler
val event: EventPacketHandler
/**
* 动作处理. 如发送好友请求, 处理别人发来的好友请求等
*
* java 调用方式: `botNetWorkHandler.getAction()`
*/
val action: ActionPacketHandler
/**
* 尝试登录
* [PacketHandler] 列表
*/
suspend fun login(): LoginState
val packetHandlers: PacketHandlerList
/**
* 尝试登录. 将会依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
*/
suspend fun login(configuration: LoginConfiguration): LoginResult
/**
* 添加一个临时包处理器
......@@ -64,5 +71,12 @@ interface BotNetworkHandler {
*/
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)
fun close()
/**
* 发送数据包
*/
suspend fun sendPacket(packet: ClientPacket)
override fun close() {
NetworkScope.cancel("handler closed", HandlerClosedException())
}
}
\ No newline at end of file
package net.mamoe.mirai.network
class HandlerClosedException : Exception()
\ No newline at end of file
package net.mamoe.mirai.network
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.getGTK
import kotlin.jvm.JvmSynthetic
/**
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
......@@ -16,8 +18,9 @@ import net.mamoe.mirai.utils.getGTK
*/
class LoginSession(
val bot: Bot,
val sessionKey: ByteArray,
val socket: DataPacketSocket
val sessionKey: ByteArray,//TODO 协议抽象? 可能并不是所有协议均需要 sessionKey
val socket: DataPacketSocket,
val scope: CoroutineScope
) {
/**
......@@ -28,6 +31,7 @@ class LoginSession(
/**
* Web api 使用
*/
@ExperimentalStdlibApi
var sKey: String = ""
set(value) {
field = value
......@@ -39,6 +43,7 @@ class LoginSession(
*/
var gtk: Int = 0
val isOpen: Boolean get() = socket.isOpen
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
......@@ -56,7 +61,7 @@ class LoginSession(
* @param P 期待的包
* @param handlerTemporary 处理器.
*/
@JvmSynthetic
//@JvmSynthetic
suspend inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableDeferred<Unit> {
val deferred = CompletableDeferred<Unit>()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary))
......@@ -89,3 +94,6 @@ class LoginSession(
return deferred
}
}
suspend fun LoginSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet)
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim
import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.*
import net.mamoe.mirai.*
import net.mamoe.mirai.event.EventScope
import net.mamoe.mirai.event.ListeningStatus
......@@ -14,93 +13,170 @@ import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.event.subscribe
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.NetworkScope
import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.utils.*
import java.io.File
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetSocketAddress
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
import javax.imageio.ImageIO
/**
* [BotNetworkHandler] 的内部实现, 该类不会有帮助理解的注解, 请查看 [BotNetworkHandler] 以获取帮助
* [BotNetworkHandler] 的 TIM PC 协议实现
*
* @see BotNetworkHandler
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")//to simplify code
internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
override val socket: BotSocket = BotSocket()
internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler<TIMBotNetworkHandler.BotSocket> {
override val NetworkScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
override lateinit var socket: BotSocket
lateinit var loginHandler: LoginHandler
override lateinit var event: EventPacketHandler
override lateinit var message: MessagePacketHandler
override lateinit var action: ActionPacketHandler
val packetHandlers: PacketHandlerList = PacketHandlerList()
override val packetHandlers: PacketHandlerList = PacketHandlerList()
internal val temporaryPacketHandlers = Collections.synchronizedList(mutableListOf<TemporaryPacketHandler<*>>())
internal val temporaryPacketHandlers = mutableListOf<TemporaryPacketHandler<*>>()
private var heartbeatJob: Job? = null
override suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) {
temporaryPacketHandler.send(action.session)
temporaryPacketHandlers.add(temporaryPacketHandler)
temporaryPacketHandler.send(action.session)
}
override suspend fun login(): LoginState {
return loginInternal(LinkedList(TIMProtocol.SERVER_IP))
}
override suspend fun login(configuration: LoginConfiguration): LoginResult {
TIMProtocol.SERVER_IP.forEach {
bot.logger.logInfo("Connecting server $it")
this.socket = BotSocket(it, configuration)
//嵌套进 login 会导致 kotlin internal CompilationException
private suspend fun loginInternal(ipQueue: LinkedList<String>): LoginState {
this.socket.close()
val ip = ipQueue.poll() ?: return LoginState.UNKNOWN//所有服务器均返回 UNKNOWN
loginResult = CompletableDeferred()
return socket.touch(ip).let { state ->
if (state == LoginState.UNKNOWN || state == LoginState.TIMEOUT) {
loginInternal(ipQueue)//超时或未知, 重试连接下一个服务器
} else {
state
val state = socket.resendTouch()
if (state != LoginResult.TIMEOUT) {
return state
}
bot.logger.logPurple("Timeout. Retrying next server")
socket.close()
}
return LoginResult.TIMEOUT
}
internal var loginResult: CompletableDeferred<LoginResult> = CompletableDeferred()
//private | internal
private fun onLoggedIn(sessionKey: ByteArray) {
val session = LoginSession(bot, sessionKey, socket)
message = MessagePacketHandler(session)
require(packetHandlers.size == 0) { "Already logged in" }
val session = LoginSession(bot, sessionKey, socket, NetworkScope)
event = EventPacketHandler(session)
action = ActionPacketHandler(session)
packetHandlers.add(message.asNode())
packetHandlers.add(event.asNode())
packetHandlers.add(action.asNode())
}
private lateinit var sessionKey: ByteArray
override fun close() {
super.close()
this.heartbeatJob?.cancel(CancellationException("handler closed"))
this.heartbeatJob = null
if (!this.loginResult.isCompleted && !this.loginResult.isCancelled) {
this.loginResult.cancel(CancellationException("socket closed"))
}
this.packetHandlers.forEach {
it.instance.close()
}
this.socket.close()
}
override suspend fun sendPacket(packet: ClientPacket) = socket.sendPacket(packet)
internal inner class BotSocket(override val serverIp: String, val configuration: LoginConfiguration) : DataPacketSocket {
override val channel: MiraiDatagramChannel = MiraiDatagramChannel(serverIp, 8000)
override val isOpen: Boolean get() = channel.isOpen
private lateinit var loginHandler: LoginHandler
private suspend fun processReceive() {
while (channel.isOpen) {
val buffer = IoBuffer.Pool.borrow()
try {
channel.read(buffer)//JVM: withContext(IO)
} catch (e: ClosedChannelException) {
} catch (e: Exception) {
e.log()
continue
}
if (!buffer.canRead() || buffer.readRemaining == 0) {//size==0
buffer.release(IoBuffer.Pool)
continue
}
NetworkScope.launch {
try {
//Ensure the packet is consumed totally so that all buffers are released
ByteReadPacket(buffer, IoBuffer.Pool).use {
distributePacket(it.parseServerPacket(buffer.readRemaining))
}
} catch (e: Exception) {
e.log()
}
}
}
}
internal suspend fun resendTouch(): LoginResult {
if (::loginHandler.isInitialized) loginHandler.close()
loginHandler = LoginHandler()
val expect = expectPacket<ServerTouchResponsePacket>()
NetworkScope.launch { processReceive() }
NetworkScope.launch {
if (withTimeoutOrNull(configuration.touchTimeoutMillis) { expect.join() } == null) {
loginResult.complete(LoginResult.TIMEOUT)
}
}
sendPacket(ClientTouchPacket(bot.qqNumber, this.serverIp))
return loginResult.await()
}
private inline fun <reified P : ServerPacket> expectPacket(): CompletableDeferred<P> {
val receiving = CompletableDeferred<P>()
subscribe<ServerPacketReceivedEvent> {
if (it.packet is P && it.bot === bot) {
receiving.complete(it.packet)
ListeningStatus.STOPPED
} else
ListeningStatus.LISTENING
}
return receiving
}
internal inner class BotSocket : DataPacketSocket {
override suspend fun distributePacket(packet: ServerPacket) {
try {
packet.decode()
} catch (e: Exception) {
e.printStackTrace()
bot.printPacketDebugging(packet)
return
packet.close()
throw e
}
//removeIf is not inline
packet.use {
//coz removeIf is not inline
with(temporaryPacketHandlers.iterator()) {
while (hasNext()) {
if (next().onPacketReceived(action.session, packet)) {
......@@ -109,10 +185,11 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
}
};
//For debug
//For logDebug
{
if (!packet.javaClass.name.endsWith("Encrypted") && !packet.javaClass.name.endsWith("Raw")) {
bot.notice("Packet received: $packet")
val name = packet::class.simpleName
if (name != null && !name.endsWith("Encrypted") && !name.endsWith("Raw")) {
bot.cyan("Packet received: $packet")
}
}()
......@@ -132,115 +209,41 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
it.instance.onPacketReceived(packet)
}
}
private var socket: DatagramSocket? = null
internal var serverIP: String = ""
set(value) {
field = value
restartSocket()
}
internal lateinit var loginResult: CompletableDeferred<LoginState>
@Synchronized
private fun restartSocket() {
socket?.close()
socket = DatagramSocket(0)
socket!!.connect(InetSocketAddress(serverIP, 8000))
NetworkScope.launch {
while (socket?.isConnected == true) {
val packet = DatagramPacket(ByteArray(2048), 2048)
kotlin.runCatching { withContext(Dispatchers.IO) { socket?.receive(packet) } }
.onSuccess {
NetworkScope.launch {
distributePacket(ServerPacket.ofByteArray(packet.data.removeZeroTail()))
}
}.onFailure {
if (it.message == "Socket closed" || it.message == "socket closed") {
return@launch
}
it.printStackTrace()
}
}
}
}
internal suspend fun touch(serverAddress: String): LoginState {
bot.info("Connecting server: $serverAddress")
restartSocket()
if (this@TIMBotNetworkHandler::loginHandler.isInitialized) {
loginHandler.close()
}
loginHandler = LoginHandler()
this.loginResult = CompletableDeferred()
serverIP = serverAddress
//bot.waitForPacket(ServerTouchResponsePacket::class, timeoutMillis) {
// loginResult?.complete(LoginState.TIMEOUT)
//}
val received = AtomicBoolean(false)
ServerPacketReceivedEvent::class.subscribe {
if (it.packet is ServerTouchResponsePacket && it.bot === bot) {
received.set(true)
ListeningStatus.STOPPED
} else
ListeningStatus.LISTENING
}
NetworkScope.launch {
delay(2000)
if (!received.get()) {
loginResult.complete(LoginState.TIMEOUT)
}
}
sendPacket(ClientTouchPacket(bot.qqNumber, serverIP))
return withContext(Dispatchers.IO) { loginResult.await() }
}
override suspend fun sendPacket(packet: ClientPacket) = withContext(NetworkScope.coroutineContext) {
checkNotNull(socket) { "network closed" }
if (socket!!.isClosed) {
return@withContext
}
packet.encodePacket()
check(channel.isOpen) { "channel is not open" }
if (BeforePacketSendEvent(bot, packet).broadcast().cancelled) {
return@withContext
}
val data = packet.toByteArray()
withContext(Dispatchers.IO) {
socket!!.send(DatagramPacket(data, data.size))
packet.packet.use { build ->
val buffer = IoBuffer.Pool.borrow()
try {
build.readAvailable(buffer)
channel.send(buffer)//JVM: withContext(IO)
} catch (e: Exception) {
e.log()
return@withContext
} finally {
buffer.release(IoBuffer.Pool)
}
}
bot.cyan("Packet sent: $packet")
EventScope.launch { PacketSentEvent(bot, packet).broadcast() }
bot.green("Packet sent: $packet")
Unit
EventScope.launch { PacketSentEvent(bot, packet).broadcast() }
}
override val owner: Bot get() = this@TIMBotNetworkHandler.bot
override fun close() {
this.socket?.close()
if (this::loginResult.isInitialized) {
if (!this.loginResult.isCompleted && !this.loginResult.isCancelled) {
this.loginResult.cancel(CancellationException("socket closed"))
if (::loginHandler.isInitialized) loginHandler.close()
this.channel.close()
}
}
}
override fun isClosed(): Boolean = this.socket?.isClosed ?: true
}
companion object {
val captchaLock = Mutex()
}
/**
* 处理登录过程
......@@ -255,40 +258,48 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
/**
* 0828_decr_key
*/
private lateinit var sessionResponseDecryptionKey: ByteArray
private lateinit var sessionResponseDecryptionKey: IoBuffer
private var captchaSectionId: Int = 1
private var captchaCache: ByteArray? = byteArrayOf()//每次包只发一部分验证码来
private var heartbeatJob: Job? = null
private var captchaCache: IoBuffer? = null
get() {
if (field == null) field = IoBuffer.Pool.borrow()
return field
}
set(value) {
if (value == null) {
field?.release(IoBuffer.Pool)
}
field = value
}
suspend fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerTouchResponsePacket -> {
if (packet.serverIP != null) {//redirection
socket.serverIP = packet.serverIP!!
//connect(packet.serverIP!!)
socket.sendPacket(ClientServerRedirectionPacket(packet.serverIP!!, bot.qqNumber))
socket.close()
socket = BotSocket(packet.serverIP!!, socket.configuration)
bot.logger.logPurple("Redirecting to ${packet.serverIP}")
loginResult.complete(socket.resendTouch())
} else {//password submission
this.loginIP = packet.loginIP
this.loginTime = packet.loginTime
this.token0825 = packet.token0825
println("token0825=" + this.token0825.toUHexString())
socket.sendPacket(ClientPasswordSubmissionPacket(bot.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.privateKey, packet.token0825))
socket.sendPacket(ClientPasswordSubmissionPacket(bot.qqNumber, bot.account.password, packet.loginTime, packet.loginIP, this.privateKey, packet.token0825, socket.configuration.randomDeviceName))
}
}
is ServerLoginResponseFailedPacket -> {
socket.loginResult.complete(packet.loginState)
loginResult.complete(packet.loginResult)
bot.close()
return
}
is ServerCaptchaCorrectPacket -> {
this.privateKey = getRandomByteArray(16)
this.privateKey = getRandomByteArray(16)//似乎是必须的
this.token00BA = packet.token00BA
socket.sendPacket(ClientLoginResendPacket3105(bot.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.privateKey, this.token0825, this.token00BA))
socket.sendPacket(ClientLoginResendPacket3105(bot.qqNumber, bot.account.password, this.loginTime, this.loginIP, this.privateKey, this.token0825, packet.token00BA, socket.configuration.randomDeviceName))
}
is ServerLoginResponseVerificationCodeInitPacket -> {
......@@ -298,7 +309,7 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
if (packet.unknownBoolean == true) {
this.captchaSectionId = 1
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.qqNumber, this.token0825, this.captchaSectionId++, this.token00BA))
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(1, bot.qqNumber, this.token0825, this.captchaSectionId++, packet.token00BA))
}
}
......@@ -306,47 +317,29 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
if (packet is ServerCaptchaWrongPacket) {
bot.error("验证码错误, 请重新输入")
captchaSectionId = 1
this.captchaCache = byteArrayOf()
this.captchaCache = null
}
this.captchaCache = this.captchaCache!! + packet.captchaSectionN
this.captchaCache!!.writeFully(packet.captchaSectionN)
this.token00BA = packet.token00BA
if (packet.transmissionCompleted) {
//todo 验证码多样化处理
val code = captchaLock.withLock {
withContext(Dispatchers.IO) {
bot.notice(ImageIO.read(captchaCache!!.inputStream()).createCharImg())
}
bot.notice("需要验证码登录, 验证码为 4 字母")
try {
File(System.getProperty("user.dir") + "/temp/Captcha.png")
.also { withContext(Dispatchers.IO) { it.createNewFile() } }
.writeBytes(this.captchaCache!!)
bot.notice("若看不清字符图片, 请查看 Mirai 目录下 /temp/Captcha.png")
} catch (e: Exception) {
bot.notice("无法写出验证码文件, 请尝试查看以上字符图片")
}
val code = solveCaptcha(captchaCache!!)
if (code == null) {
this.captchaCache = null
bot.notice("若要更换验证码, 请直接回车")
Scanner(System.`in`).nextLine()
}
if (code.isEmpty() || code.length != 4) {
this.captchaCache = byteArrayOf()
this.captchaSectionId = 1
socket.sendPacket(ClientVerificationCodeRefreshPacket(packet.packetIdLast + 1, bot.qqNumber, token0825))
} else {
socket.sendPacket(ClientVerificationCodeSubmitPacket(packet.packetIdLast + 1, bot.qqNumber, token0825, code, packet.verificationToken))
}
} else {
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.qqNumber, token0825, captchaSectionId++, token00BA))
socket.sendPacket(ClientVerificationCodeTransmissionRequestPacket(packet.packetIdLast + 1, bot.qqNumber, token0825, captchaSectionId++, packet.token00BA))
}
}
is ServerLoginResponseSuccessPacket -> {
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
socket.sendPacket(ClientSessionRequestPacket(bot.qqNumber, socket.serverIP, packet.token38, packet.token88, packet.encryptionKey))
socket.sendPacket(ClientSessionRequestPacket(bot.qqNumber, socket.serverIp, packet.token38, packet.token88, packet.encryptionKey))
}
//是ClientPasswordSubmissionPacket之后服务器回复的
......@@ -358,10 +351,10 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
if (packet.flag == ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`) {
this.privateKey = packet.privateKeyUpdate
socket.sendPacket(ClientLoginResendPacket3104(bot.qqNumber, bot.account.password, loginTime, loginIP, privateKey, token0825, packet.tokenUnknown
?: this.token00BA, packet.tlv0006))
?: token00BA, socket.configuration.randomDeviceName, packet.tlv0006))
} else {
socket.sendPacket(ClientLoginResendPacket3106(bot.qqNumber, bot.account.password, loginTime, loginIP, privateKey, token0825, packet.tokenUnknown
?: token00BA, packet.tlv0006))
?: token00BA, socket.configuration.randomDeviceName, packet.tlv0006))
}
}
......@@ -369,13 +362,15 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
sessionKey = packet.sessionKey
heartbeatJob = NetworkScope.launch {
while (socket.isOpen) {
delay(90000)
socket.sendPacket(ClientHeartbeatPacket(bot.qqNumber, sessionKey))
}
}
socket.loginResult.complete(LoginState.SUCCESS)
loginResult.complete(LoginResult.SUCCESS)
loginHandler.changeOnlineStatus(ClientLoginStatus.ONLINE)//required
setOnlineStatus(OnlineStatus.ONLINE)//required
}
is ServerLoginSuccessPacket -> {
......@@ -384,11 +379,12 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
//登录成功后会收到大量上次的消息, 忽略掉 todo 优化
NetworkScope.launch {
delay(3000)
message.ignoreMessage = false
event.ignoreMessage = false
}
onLoggedIn(sessionKey)
this.close()//The LoginHandler is useless since then
}
......@@ -400,10 +396,11 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
is ServerTouchResponsePacket.Encrypted -> socket.distributePacket(packet.decrypt())
is ServerHeartbeatResponsePacket,
is UnknownServerPacket -> {
//ignored
is ServerHeartbeatResponsePacket -> {
}
is UnknownServerPacket.Encrypted -> socket.distributePacket(packet.decrypt(sessionKey))
else -> {
}
......@@ -411,16 +408,14 @@ internal class TIMBotNetworkHandler(private val bot: Bot) : BotNetworkHandler {
}
@Suppress("MemberVisibilityCanBePrivate")
suspend fun changeOnlineStatus(status: ClientLoginStatus) {
suspend fun setOnlineStatus(status: OnlineStatus) {
socket.sendPacket(ClientChangeOnlineStatusPacket(bot.qqNumber, sessionKey, status))
}
fun close() {
this.captchaCache = null
this.heartbeatJob?.cancel(CancellationException("handler closed"))
this.heartbeatJob = null
if (::sessionResponseDecryptionKey.isInitialized) this.sessionResponseDecryptionKey.release(IoBuffer.Pool)
}
}
}
......@@ -2,17 +2,9 @@
package net.mamoe.mirai.network.protocol.tim
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import java.net.InetAddress
import java.util.*
import java.util.stream.Collectors
import net.mamoe.mirai.utils.solveIpAddress
/**
* @author Him188moe
*/
object TIMProtocol {
val SERVER_IP: List<String> = {
//add("183.60.56.29")
......@@ -25,7 +17,7 @@ object TIMProtocol {
"sz8.tencent.com",
"sz9.tencent.com",
"sz2.tencent.com"
).forEach { list.add(InetAddress.getByName(it).hostAddress) }
).forEach { list.add(solveIpAddress(it)) }
list.toList()
}()
......@@ -45,7 +37,7 @@ object TIMProtocol {
const val constantData2 = "00 00 04 53 00 00 00 01 00 00 15 85 "
/**
* Touch 发出时写入, 并用于加密, 接受 touch response 时解密.
* Touch 发出时写入, 并用于加密, 接受 sendTouch response 时解密.
*/
const val touchKey = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"//16
......@@ -66,11 +58,6 @@ object TIMProtocol {
*/
const val key0836 = "EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA"//16
@JvmStatic
fun main(args: Array<String>) {
println(TEA.decrypt(publicKey.hexToBytes(), key0836).toUHexString())
}
/**
* 并非常量. 是 publicKey 与 key0836 的算法计算结果
*/
......@@ -100,27 +87,4 @@ object TIMProtocol {
*/
const val messageConst1 = "00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
// TIM最新 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
fun hexToBytes(hex: String): ByteArray {
hex.hashCode().let { id ->
if (hexToByteArrayCacheMap.containsKey(id)) {
return hexToByteArrayCacheMap[id]!!.clone()
} else {
hexToUBytes(hex).toByteArray().let {
hexToByteArrayCacheMap[id] = it.clone()
return it
}
}
}
}
fun hexToUBytes(hex: String): UByteArray = Arrays
.stream(hex.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
.map { value -> value.trim { it <= ' ' } }
.map { s -> s.toUByte(16) }
.collect(Collectors.toList()).toUByteArray()
}
package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientAccountInfoRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerAccountInfoResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.AddFriendResult
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientCanAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.getGTK
import java.awt.image.BufferedImage
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import net.mamoe.mirai.utils.hexToBytes
/**
* 动作: 获取好友列表, 点赞, 踢人等.
......@@ -32,12 +23,13 @@ import java.util.function.Supplier
* @author Him188moe
*/
class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
private val addFriendSessions = mutableListOf<AddFriendSession>()
private val uploadImageSessions = mutableListOf<UploadImageSession>()
private var sKeyRefresherFuture: ScheduledFuture<*>? = null
private var sKeyRefresherJob: Job? = null
@ExperimentalStdlibApi
override suspend fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerCanAddFriendResponsePacket -> {
......@@ -65,11 +57,13 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
session.sKey = packet.sKey
session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.sKey + ";"
sKeyRefresherFuture = MiraiThreadPool.instance.scheduleWithFixedDelay({
runBlocking {
sKeyRefresherJob = session.scope.launch {
while (session.isOpen) {
delay(1800000)
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey))
}
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
}
session.gtk = getGTK(session.sKey)
}
......@@ -82,15 +76,9 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
}
}
suspend fun addFriend(qqNumber: Long, message: Supplier<String>) {
addFriend(qqNumber, lazy { message.get() })
}
@JvmSynthetic
suspend fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
val future = CompletableFuture<AddFriendResult>()
//@JvmSynthetic
suspend fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableDeferred<AddFriendResult> {
val future = CompletableDeferred<AddFriendResult>()
val session = AddFriendSession(qqNumber, future, message)
// uploadImageSessions.add(session)
session.sendAddRequest()
......@@ -108,14 +96,14 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
}
override fun close() {
this.sKeyRefresherFuture?.cancel(true)
this.sKeyRefresherFuture = null
this.sKeyRefresherJob?.cancel()
this.sKeyRefresherJob = null
}
private inner class UploadImageSession(
private val group: Long,
private val future: CompletableFuture<AddFriendResult>,
private val image: BufferedImage
private val future: CompletableDeferred<AddFriendResult>
//private val image: BufferedImage
) {
lateinit var id: ByteArray
......@@ -167,7 +155,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private inner class AddFriendSession(
private val qq: Long,
private val future: CompletableFuture<AddFriendResult>,
private val future: CompletableDeferred<AddFriendResult>,
private val message: Lazy<String>
) {
lateinit var id: ByteArray
......@@ -180,7 +168,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
if (!(packet.idByteArray.contentEquals(id))) {
return
}
......@@ -211,7 +199,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
suspend fun sendAddRequest() {
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.packetIdLast })
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.idHex.hexToBytes() })
}
fun close() {
......
package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.io.core.Closeable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.MiraiDatagramChannel
/**
* 网络接口.
......@@ -14,9 +16,21 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
*
* @author Him188moe
*/
interface DataPacketSocket {
interface DataPacketSocket : Closeable {
val owner: Bot
val serverIp: String
/**
* UDP 通道
*/
val channel: MiraiDatagramChannel
/**
* 是否开启
*/
val isOpen: Boolean
/**
* 分发数据包给 [PacketHandler]
*/
......@@ -31,7 +45,5 @@ interface DataPacketSocket {
*/
suspend fun sendPacket(packet: ClientPacket)
fun isClosed(): Boolean
fun close()
override fun close()
}
\ No newline at end of file
......@@ -9,14 +9,13 @@ import net.mamoe.mirai.getGroupByNumber
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerFriendMessageEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupMessageEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupUploadFileEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.distributePacket
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientSendFriendMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientSendGroupMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.utils.MiraiLogger
/**
* 处理消息事件, 承担消息发送任务.
......@@ -24,10 +23,10 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessage
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")
class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
class EventPacketHandler(session: LoginSession) : PacketHandler(session) {
internal var ignoreMessage: Boolean = true
override suspend fun onPacketReceived(packet: ServerPacket) {
override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) {
when (packet) {
is ServerGroupUploadFileEventPacket -> {
//todo
......@@ -36,21 +35,28 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
is ServerFriendMessageEventPacket -> {
if (ignoreMessage) return
FriendMessageEvent(session.bot, session.bot.getQQ(packet.qq), packet.message).broadcast()
FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message).broadcast()
}
is ServerGroupMessageEventPacket -> {
if (ignoreMessage) return
if (packet.qq == session.bot.account.qqNumber) return
if (packet.qq.toLong() == bot.account.qqNumber) return
GroupMessageEvent(session.bot, session.bot.getGroupByNumber(packet.groupNumber), session.bot.getQQ(packet.qq), packet.message).broadcast()
GroupMessageEvent(bot, bot.getGroupByNumber(packet.groupNumber), bot.getQQ(packet.qq), packet.message).broadcast()
}
is ServerSendFriendMessageResponsePacket,
is ServerSendGroupMessageResponsePacket -> {
//ignored
}
is ServerFieldOnlineStatusChangedPacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
is ServerFieldOnlineStatusChangedPacket -> {
MiraiLogger.logInfo("${packet.qq.toLong()} 登录状态改变为 ${packet.status}")
//TODO
}
else -> {
//ignored
}
......
......@@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.handler
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import kotlin.reflect.KClass
/**
* 数据包(接受/发送)处理器
......@@ -17,17 +18,18 @@ abstract class PacketHandler(
}
class PacketHandlerNode<T : PacketHandler>(
val clazz: Class<T>,
val clazz: KClass<T>,
val instance: T
)
fun PacketHandler.asNode(): PacketHandlerNode<PacketHandler> {
return PacketHandlerNode(this.javaClass, this)
fun <T : PacketHandler> T.asNode(): PacketHandlerNode<T> {
@Suppress("UNCHECKED_CAST")
return PacketHandlerNode(this::class as KClass<T>, this)
}
class PacketHandlerList : MutableList<PacketHandlerNode<*>> by mutableListOf() {
fun <T : PacketHandler> get(clazz: Class<T>): T {
operator fun <T : PacketHandler> get(clazz: KClass<T>): T {
this.forEach {
if (it.clazz == clazz) {
@Suppress("UNCHECKED_CAST")
......
......@@ -4,7 +4,6 @@ import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
/**
......@@ -20,7 +19,7 @@ import kotlin.reflect.KClass
*
* @see LoginSession.expectPacket
*/
open class TemporaryPacketHandler<P : ServerPacket>(
class TemporaryPacketHandler<P : ServerPacket>(
private val expectationClass: KClass<P>,
private val deferred: CompletableDeferred<Unit>,
private val fromSession: LoginSession
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.writeHex
//TODO 将序列 ID 从包 ID 中独立出来
abstract class ClientPacket : Packet(), Closeable {
/**
* Encode this packet.
*
* Before sending the packet, a [tail][TIMProtocol.tail] is added.
*/
protected abstract fun encode(builder: BytePacketBuilder)
companion object {
@Suppress("PrivatePropertyName")
private val UninitializedByteReadPacket = ByteReadPacket(IoBuffer.Empty, IoBuffer.EmptyPool)
}
/**
* 务必 [ByteReadPacket.close] 或 [close] 或使用 [Closeable.use]
*/
var packet: ByteReadPacket = UninitializedByteReadPacket
get() {
if (field === UninitializedByteReadPacket) build()
return field
}
private fun build(): ByteReadPacket {
packet = buildPacket {
writeHex(TIMProtocol.head)
writeHex(TIMProtocol.ver)
writeHex(idHex)
encode(this)
writeHex(TIMProtocol.tail)
}
return packet
}
override fun toString(): String = packetToString()
override fun close() = if (this.packet === UninitializedByteReadPacket) Unit else this.packet.close()
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.utils.OnlineStatus
import kotlin.properties.Delegates
/**
* 好友在线状态改变
*/
@PacketId("00 81")
class ServerFieldOnlineStatusChangedPacket(input: ByteReadPacket) : ServerPacket(input) {
var qq: UInt by Delegates.notNull()
lateinit var status: OnlineStatus
override fun decode() = with(input) {
qq = readUInt()
discardExact(8)
status = OnlineStatus.ofId(readUByte())
}
//在线 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
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerFieldOnlineStatusChangedPacket = ServerFieldOnlineStatusChangedPacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import java.io.DataInputStream
import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ
import net.mamoe.mirai.utils.writeRandom
/**
* 获取升级天数等.
......@@ -14,13 +22,13 @@ class ClientAccountInfoRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
writeByte(0x88)
writeUByte(0x88.toUByte())
writeQQ(qq)
writeByte(0x00)
}
......@@ -28,7 +36,7 @@ class ClientAccountInfoRequestPacket(
}
@PacketId("00 5C")
class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(input) {
class ServerAccountInfoResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
//等级
//升级剩余活跃天数
//ignored
......@@ -37,7 +45,7 @@ class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(inp
}
@PacketId("00 5C")
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerAccountInfoResponsePacket = ServerAccountInfoResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import java.io.DataInputStream
import java.io.IOException
import net.mamoe.mirai.utils.*
/**
* @author Him188moe
*/
@PacketId("00 58")
class ClientHeartbeatPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
@Throws(IOException::class)
override fun encode() {
this.writeRandom(2)
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.encryptAndWrite(sessionKey) {
......@@ -24,4 +24,4 @@ class ClientHeartbeatPacket(
}
}
class ServerHeartbeatResponsePacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerHeartbeatResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.utils.hexToUBytes
abstract class Packet {
open val idHex: String by lazy {
this::class.annotations.filterIsInstance<PacketId>().firstOrNull()?.value?.trim() ?: ""
}
open val fixedId: String by lazy {
when (this.idHex.length) {
0 -> "__ __ __ __"
2 -> this.idHex + " __ __ __"
5 -> this.idHex + " __ __"
7 -> this.idHex + " __"
else -> this.idHex
}
}
open val idByteArray: ByteArray by lazy {
idHex.hexToUBytes().toByteArray()
}
}
internal expect fun Packet.packetToString(): String
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
/**
* @author Him188moe
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
annotation class PacketId(
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.*
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.dataDecode
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import net.mamoe.mirai.utils.toUInt
import java.io.DataInputStream
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
data class EventPacketIdentity(
val from: UInt,//对于好友消息, 这个是发送人
val to: UInt,//对于好友消息, 这个是bot
internal val uniqueId: IoBuffer//8
) {
override fun toString(): String = "EPI(from=$from, to=$to)"
}
fun BytePacketBuilder.writeEventPacketIdentity(identity: EventPacketIdentity) = with(identity) {
writeUInt(from)
writeUInt(to)
writeFully(uniqueId)
}
/**
* Packet id: `00 CE` or `00 17`
*
* @author Him188moe
*/
abstract class ServerEventPacket(input: DataInputStream, val packetId: ByteArray, val eventIdentity: ByteArray) : ServerPacket(input) {
@PacketId("00 17")
class Raw(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) {
fun distribute(): ServerEventPacket {
val eventIdentity = this.input.readNBytes(16)
val type = this.input.goto(18).readNBytes(2)
abstract class ServerEventPacket(input: ByteReadPacket, packetId: ByteArray, val eventIdentity: EventPacketIdentity) : ServerPacket(input) {
override val idByteArray: ByteArray = packetId
override var idHex: String = packetId.toUHexString()
class Raw(input: ByteReadPacket, private val packetId: ByteArray) : ServerPacket(input) {
fun distribute(): ServerEventPacket = with(input) {
val eventIdentity = EventPacketIdentity(
from = readUInt(),
to = readUInt(),
uniqueId = readIoBuffer(8)
)
readBytes(2).takeIf { it[0].toUInt() != 0x1Fu && it[1].toUInt() != 0x40u }?.debugPrint("type前面2个byte")
val type = readBytes(2)
return when (type.toUHexString()) {
"00 C4" -> {
if (this.input.goto(33).readBoolean()) {
ServerAndroidOnlineEventPacket(this.input, packetId, eventIdentity)
discardExact(13)
if (readBoolean()) {
ServerAndroidOnlineEventPacket(input, packetId, eventIdentity)
} else {
ServerAndroidOfflineEventPacket(this.input, packetId, eventIdentity)
ServerAndroidOfflineEventPacket(input, packetId, eventIdentity)
}
}
"00 2D" -> ServerGroupUploadFileEventPacket(this.input, packetId, eventIdentity)
"00 2D" -> ServerGroupUploadFileEventPacket(input, packetId, eventIdentity)
"00 52" -> ServerGroupMessageEventPacket(this.input, packetId, eventIdentity)
"00 52" -> ServerGroupMessageEventPacket(input, packetId, eventIdentity)
"00 A6" -> ServerFriendMessageEventPacket(this.input, packetId, eventIdentity)
"00 A6" -> ServerFriendMessageEventPacket(input, packetId, eventIdentity)
//"02 10", "00 12" -> ServerUnknownEventPacket(this.input, packetId, eventIdentity)
//"02 10", "00 12" -> ServerUnknownEventPacket(input, packetId, eventIdentity)
else -> UnknownServerEventPacket(this.input, packetId, eventIdentity)
}.setId(this.idHex)
else -> UnknownServerEventPacket(input, packetId, eventIdentity)
}.setId(idHex)
}
@PacketId("00 17")
class Encrypted(input: DataInputStream, private val packetId: ByteArray) : ServerPacket(input) {
class Encrypted(input: ByteReadPacket, private val packetId: ByteArray) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey), packetId).setId(this.idHex)
}
}
@PacketId("")
inner class ResponsePacket(
val qq: Long,
val bot: Long,
val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.write(packetId)//packet id 4bytes
override val idHex: String = this@ServerEventPacket.idHex
override val idByteArray: ByteArray = this@ServerEventPacket.idByteArray
override val fixedId: String = idHex
this.writeQQ(qq)
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
write(eventIdentity)
writeEventPacketIdentity(eventIdentity)
}
}
override fun getFixedId(): String {
return packetId.toUHexString()
}
}
}
/**
* Unknown event
*/
class UnknownServerEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
class UnknownServerEventPacket(input: ByteReadPacket, packetId: ByteArray, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, packetId, eventIdentity) {
override fun decode() {
super.decode()
println("UnknownServerEventPacket data: " + this.input.goto(0).readAllBytes().toUHexString())
println("UnknownServerEventPacket data: " + this.input.readBytes().toUHexString())
}
}
/**
* Android 客户端上线
*/
class ServerAndroidOnlineEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity)
class ServerAndroidOnlineEventPacket(input: ByteReadPacket, packetId: ByteArray, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, packetId, eventIdentity)
/**
* Android 客户端下线
*/
class ServerAndroidOfflineEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity)
class ServerAndroidOfflineEventPacket(input: ByteReadPacket, packetId: ByteArray, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, packetId, eventIdentity)
/**
* 群文件上传
*/
class ServerGroupUploadFileEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
class ServerGroupUploadFileEventPacket(input: ByteReadPacket, packetId: ByteArray, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, packetId, eventIdentity) {
private lateinit var xmlMessage: String
override fun decode() {
xmlMessage = String(this.input.goto(65).readNBytes(this.input.goto(60).readShort().toInt()))
this.input.discardExact(60)
val size = this.input.readShort().toInt()
this.input.discardExact(3)
xmlMessage = this.input.readString(size)
}//todo test
}
@Suppress("EXPERIMENTAL_API_USAGE")
class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
var groupNumber: Long = 0
var qq: Long = 0
class ServerGroupMessageEventPacket(input: ByteReadPacket, packetId: ByteArray, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, packetId, eventIdentity) {
var groupNumber: UInt by Delegates.notNull()
var qq: UInt by Delegates.notNull()
lateinit var senderName: String
lateinit var message: MessageChain
enum class MessageType {
NORMAL,
XML,
AT,
FACE,//qq自带表情 [face107.gif]
PLAIN_TEXT, //纯文本
IMAGE, //自定义图片 {F50C5235-F958-6DF7-4EFA-397736E125A4}.gif
ANONYMOUS,//匿名用户发出的消息
OTHER,
}
override fun decode() {
println(this.input.goto(0).readAllBytes().toUHexString())
groupNumber = this.input.goto(51).readInt().toLong()
qq = this.input.goto(56).readNBytes(4).toUInt().toLong()
override fun decode() = with(input) {
discardExact(31)
groupNumber = readUInt()
discardExact(1)
qq = readUInt()
this.input.goto(108)
this.input.readLVByteArray()
input.skip(2)//2个0x00
message = input.readMessageChain()
discardExact(48)
readLVByteArray()
discardExact(2)//2个0x00
message = readMessageChain()
val map = input.readTLVMap(true)
if (map.containsKey(18)) {
this.senderName = dataDecode(map.getValue(18)) {
val tlv = it.readTLVMap(true)
tlv.printTLVMap()
val map = readTLVMap(true)
map.printTLVMap("父map")
if (map.containsKey(0x18)) {
senderName = map.getValue(0x18).read {
val tlv = readTLVMap(true)
tlv.printTLVMap("子map")
when {
tlv.containsKey(0x01) -> String(tlv.getValue(0x01))
......@@ -147,128 +152,37 @@ class ServerGroupMessageEventPacket(input: DataInputStream, packetId: ByteArray,
}
}
}
/*
messageType = when (val id = this.input.goto(110 + fontLength + 2).readByte().toInt()) {
0x13 -> MessageType.NORMAL
0x0E -> MessageType.XML
0x06 -> MessageType.AT
0x01 -> MessageType.PLAIN_TEXT
0x02 -> MessageType.FACE
0x03 -> MessageType.IMAGE
0x19 -> MessageType.ANONYMOUS
else -> {
MiraiLogger.debug("ServerGroupMessageEventPacket id=$id")
MessageType.OTHER
}
}*/
/*
when (messageType) {
MessageType.NORMAL -> {
val gzippedMessage = this.input.goto(110 + fontLength + 16).readNBytes(this.input.goto(110 + fontLength + 3).readShort().toInt() - 11)
ByteArrayOutputStream().let {
GZIPInputStream(gzippedMessage.inputStream()).transferTo(it)
message = String(it.toByteArray())
}
}
MessageType.XML -> {
val gzippedMessage = this.input.goto(110 + fontLength + 9).readNBytes(this.input.goto(110 + fontLength + 3).readShort().toInt() - 4)
ByteArrayOutputStream().let {
GZIPInputStream(gzippedMessage.inputStream()).transferTo(it)
message = String(it.toByteArray())
}
}
MessageType.FACE -> {
val faceId = this.input.goto(110 + fontLength + 8).readByte()
message = "[face${faceId}.gif]"
}
MessageType.AT, MessageType.OTHER, MessageType.PLAIN_TEXT, MessageType.IMAGE, MessageType.ANONYMOUS -> {
var messageLength: Int = this.input.goto(110 + fontLength + 6).readShort().toInt()
message = String(this.input.goto(110 + fontLength + 8).readNBytes(messageLength))
val oeLength: Int
if (this.input.readByte().toInt() == 6) {
oeLength = this.input.readShort().toInt()
this.input.skip(4)
val messageLength2 = this.input.readShort().toInt()
val message2 = String(this.input.readNBytes(messageLength2))
message += message2
messageLength += messageLength2
} else {
oeLength = this.input.readShort().toInt()
}
//读取 nick, ignore.
/*
when (this.input.goto(110 + fontLength + 3 + oeLength).readByteAt().toInt()) {
12 -> {
this.input.skip(4)//maybe 5?
}
19 -> {
}
0x0E -> {
}
else -> {
}
}*/
}
}*/
}
}
fun main() {
println(String("E7 BE A4".hexToBytes()))
println(".".toByteArray().toUByteArray().toUHexString())
//长文本 22 96 29 7B B4 DF 94 AA 00 01 9F 8E 09 18 85 5B 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7E F3 5D 7B 97 57 00 00 F3 32 00 B8 00 01 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B 97 56 7F D0 53 BB 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 12 01 00 0F E9 95 BF E6 96 87 E6 9C AC E6 B6 88 E6 81 AF 0E 00 0E 01 00 04 00 00 00 09 07 00 04 00 00 00 01 19 00 35 01 00 32 AA 02 2F 50 03 60 00 68 00 9A 01 26 08 09 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 05 00 04 00 00 00 01 08 00 04 00 00 00 01 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
val packet = ServerGroupMessageEventPacket(("" +
"22 96 29 7B B4 DF 94 AA 00 09 8F 37 0A 65 07 2E 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7F 67 5D 7B AE D7 00 00 F3 36 02 E7 00 02 02 00 1B 10 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B AE D6 F4 91 87 BE 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 E7 89 9B E9 80 BC 03 00 CB 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 83 81 3B E2 05 00 04 B8 8B 33 79 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 5C 15 36 20 39 32 6B 41 31 43 38 33 38 31 33 62 65 32 62 38 38 62 33 33 37 39 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 41 01 00 09 01 00 06 E7 89 9B E9 80 BC 03 00 77 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 83 81 3B E2 05 00 04 B8 8B 33 79 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 08 15 37 20 20 38 41 41 41 02 00 14 01 00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0 03 00 CE 02 00 2A 7B 31 46 42 34 43 32 35 45 2D 42 34 46 45 2D 31 32 45 34 2D 46 33 42 42 2D 38 31 39 31 33 37 42 44 39 39 30 39 7D 2E 6A 70 67 04 00 04 B8 27 4B C6 05 00 04 79 5C B1 A3 06 00 04 00 00 00 50 07 00 01 41 08 00 00 09 00 01 01 0B 00 00 14 00 04 03 00 00 00 15 00 04 00 00 00 4E 16 00 04 00 00 00 23 18 00 04 00 00 02 A2 FF 00 5F 15 36 20 39 35 6B 44 31 41 62 38 32 37 34 62 63 36 37 39 35 63 62 31 61 33 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 31 46 42 34 43 32 35 45 2D 42 34 46 45 2D 31 32 45 34 2D 46 33 42 42 2D 38 31 39 31 33 37 42 44 39 39 30 39 7D 2E 6A 70 67 41 42 43 41 0E 00 07 01 00 04 00 00 00 09 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" +
"").hexToBytes().dataInputStream(), byteArrayOf(), byteArrayOf())
packet.decode()
println(packet)
}
//牛逼[图片]牛逼[图片] 22 96 29 7B B4 DF 94 AA 00 08 74 A4 09 18 8D CC 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7F 64 5D 7B AC BD 00 00 F3 36 02 03 00 02 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B AC BD 12 73 DB A2 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 E7 89 9B E9 80 BC 03 00 CB 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 B4 52 77 F1 05 00 04 BC EB 03 B7 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 5C 15 36 20 39 32 6B 41 31 43 62 34 35 32 37 37 66 31 62 63 65 62 30 33 62 37 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 41 01 00 09 01 00 06 E7 89 9B E9 80 BC 03 00 77 02 00 2A 7B 37 41 41 34 42 33 41 41 2D 38 43 33 43 2D 30 46 34 35 2D 32 44 39 42 2D 37 46 33 30 32 41 30 41 43 45 41 41 7D 2E 6A 70 67 04 00 04 B4 52 77 F1 05 00 04 BC EB 03 B7 06 00 04 00 00 00 50 07 00 01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 00 00 00 00 15 00 04 00 00 00 41 16 00 04 00 00 00 34 18 00 04 00 00 03 73 FF 00 08 15 37 20 20 38 41 41 41 0E 00 0E 01 00 04 00 00 00 09 07 00 04 00 00 00 01 19 00 35 01 00 32 AA 02 2F 50 03 60 00 68 00 9A 01 26 08 09 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 05 00 04 00 00 00 01 08 00 04 00 00 00 01 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
//牛逼[图片]牛逼 22 96 29 7B B4 DF 94 AA 00 0B C1 0A 09 18 89 93 1F 40 00 52 00 00 00 1B 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 0C 00 05 00 01 00 01 01 22 96 29 7B 01 3E 03 3F A2 00 03 7E F5 5D 7B 97 E7 00 00 F3 32 01 8D 00 02 01 00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00 5D 7B 97 E6 FA BE 7F DC 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 E7 89 9B E9 80 BC 03 00 CF 02 00 2A 7B 39 44 32 44 45 39 31 41 2D 33 39 38 39 2D 39 35 35 43 2D 44 35 42 34 2D 37 46 41 32 37 38 39 37 38 36 30 39 7D 2E 6A 70 67 04 00 04 97 15 7F 03 05 00 04 79 5C B1 A3 06 00 04 00 00 00 50 07 00 01 41 08 00 00 09 00 01 01 0B 00 00 14 00 04 03 00 00 00 15 00 04 00 00 00 3C 16 00 04 00 00 00 40 18 00 04 00 00 03 CC FF 00 60 15 36 20 39 36 6B 45 31 41 39 37 31 35 37 66 30 33 37 39 35 63 62 31 61 33 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 39 44 32 44 45 39 31 41 2D 33 39 38 39 2D 39 35 35 43 2D 44 35 42 34 2D 37 46 41 32 37 38 39 37 38 36 30 39 7D 2E 6A 70 67 31 32 31 32 41 01 00 09 01 00 06 E7 89 9B E9 80 BC 0E 00 0E 01 00 04 00 00 00 09 07 00 04 00 00 00 01 19 00 35 01 00 32 AA 02 2F 50 03 60 00 68 00 9A 01 26 08 09 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 05 00 04 00 00 00 01 08 00 04 00 00 00 01 01 00 09 48 69 6D 31 38 38 6D 6F 65 03 00 01 04 04 00 04 00 00 00 08
class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
var qq: Long = 0
lateinit var message: MessageChain
class ServerFriendMessageEventPacket(input: ByteReadPacket, packetId: ByteArray, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, packetId, eventIdentity) {
val group: UInt get() = eventIdentity.from
val qq: UInt get() = eventIdentity.to
override fun decode() {
input.goto(0)
println("ServerFriendMessageEventPacket.input=" + input.readAllBytes().toUHexString())
input.goto(0)
lateinit var message: MessageChain
qq = input.readUIntAt(0).toLong()
//00 00 00 25 00 08 00 02 00 01 00 09 00 06 00 01 00 00 00 01 00 0A 00 04 01 00 00 00 00 01 00 04 00 00 00 00 00 03 00 01 01 38 03 3E 03 3F A2 76 E4 B8 DD E7 86 74 F2 64 55 AD 9A EB 2F B9 DF F1 7F 8C 28 00 0B 78 14 5D A2 F5 CB 01 1D 00 00 00 00 01 00 00 00 01 4D 53 47 00 00 00 00 00 5D A2 F5 CA 9D 26 CB 5E 00 00 00 00 0C 00 86 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 00 00 01 00 09 01 00 06 E4 BD A0 E5 A5 BD 0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00
val l1 = input.readShortAt(22)
input.goto(93 + l1)
input.readLVByteArray()//font
input.skip(2)//2个0x00
message = input.readMessageChain()
override fun decode() = with(input) {
input.discardExact(2)
val l1 = readShort()
discardExact(l1.toInt())
discardExact(69)
readLVByteArray()//font
discardExact(2)//2个0x00
message = readMessageChain()
val map: Map<Int, ByteArray> = input.readTLVMap(true).withDefault { byteArrayOf() }
println(map.getValue(18))
val map: Map<Int, ByteArray> = readTLVMap(true).withDefault { byteArrayOf() }
println("map.getValue(18)=" + map.getValue(18))
//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
message = MessageChain(PlainText(let {
event = MessageChain(PlainText(let {
val length = input.readShortAt(101 + offset)//
input.goto(103 + offset).readString(length.toInt())
}))*/
......@@ -318,9 +232,9 @@ B1 89 BE 09 8F 00 1A E5 00 0B 03 A2 09 90 BB 7A 1F 40 00 A6 00 00 00 20 00 05 00
backup
class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray, eventIdentity: ByteArray) : ServerEventPacket(input, packetId, eventIdentity) {
class ServerFriendMessageEventPacket(input: ByteReadPacket, packetId: ByteArray, eventIdentity: EventPacketIdentity) : ServerEventPacket(input, packetId, eventIdentity) {
var qq: Long = 0
lateinit var message: String
lateinit var event: String
......@@ -330,7 +244,7 @@ class ServerFriendMessageEventPacket(input: DataInputStream, packetId: ByteArray
val msgLength = input.readShortAt(22)
val fontLength = input.readShortAt(93+msgLength)
val offset = msgLength+fontLength
message = if(input.readByteAt(97+offset).toUHexString() == "02"){
event = if(input.readByteAt(97+offset).toUHexString() == "02"){
"[face" + input.goto(103+offset).readByteAt(1).toInt().toString() + ".gif]"
//.gif
}else {
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.*
abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override var idHex: String = EMPTY_ID_HEX
get() {
if (field === EMPTY_ID_HEX) {
idHex = (this::class.annotations.firstOrNull { it::class == PacketId::class } as? PacketId)?.value?.trim()
?: ""
}
return field
}
var encoded: Boolean = false
open fun decode() {
}
override fun close() = this.input.close()
companion object {
private const val EMPTY_ID_HEX = "EMPTY_ID_HEX"
}
override fun toString(): String = this.packetToString()
fun getFixedId(id: String): String = when (id.length) {
0 -> "__ __ __ __"
2 -> "$id __ __ __"
5 -> "$id __ __"
7 -> "$id __"
else -> id
}
fun decryptBy(key: ByteArray): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun decryptBy(key: IoBuffer): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun decryptBy(keyHex: String): ByteReadPacket {
return this.decryptBy(keyHex.hexToBytes())
}
fun decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket {
return TEA.decrypt(this.decryptAsByteArray(key1), key2).toReadPacket()
}
fun decryptBy(key1: String, key2: ByteArray): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2)
}
fun decryptBy(key1: String, key2: IoBuffer): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2.readBytes())
}
fun decryptBy(key1: ByteArray, key2: String): ByteReadPacket {
return this.decryptBy(key1, key2.hexToBytes())
}
fun decryptBy(keyHex1: String, keyHex2: String): ByteReadPacket {
return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
}
fun decryptAsByteArray(key: ByteArray): ByteArray {
return TEA.decrypt(input.readRemainingBytes().cutTail(1), key)
}
fun decryptAsByteArray(keyHex: String): ByteArray = this.decryptAsByteArray(keyHex.hexToBytes())
fun decryptAsByteArray(buffer: IoBuffer): ByteArray = this.decryptAsByteArray(buffer.readBytes())
}
fun <P : ServerPacket> P.setId(idHex: String): P {
this.idHex = idHex
return this
}
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toUHexString
class UnknownServerPacket(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() {
val raw = this.input.readBytes()
MiraiLogger.logDebug("UnknownServerPacket data: " + raw.toUHexString())
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): UnknownServerPacket = UnknownServerPacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
override fun toString(): String {
return LoggerTextFormat.LIGHT_RED.toString() + super.toString()
}
}
/*
ID: 00 17
长度 95
76 E4 B8 DD //1994701021
76 E4 B8 DD //1994701021
00 0B B9 A9 09 90 BB 54 1F //类似Event的uniqueId?
40 02
10 00 00 00
18 00
08 00
02 00
01 00
09 00
06 41 4B DA 4C 00 00
00 0A
00 04
01 00 00 00 00 00
00 06 00 00
00 0E
08 02
1A 02 08 49 0A 0C 08 A2 FF 8C F0
03 10 CA EB 8B ED 05
或者
长度63
00 00 27 10 76 E4 B8 DD
00 09 ED 26 64 73 0E CA 1F 40
00 12 00 00
00 08
00 0A
00 04
01 00 00
00 02
值都是一样的.
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.gotoWhere
import net.mamoe.mirai.utils.toReadPacket
expect class PlatformImage
expect class ClientTryGetImageIDPacket(
botNumber: Long,
sessionKey: ByteArray,
groupNumberOrQQNumber: Long,
image: PlatformImage
) : ClientPacket
abstract class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey)
println(data.size)
println(data.size)
if (data.size == 209) {
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).setId(this.idHex)
}
return ServerTryGetImageIDFailedPacket(data.toReadPacket())
}
}
}
/**
* 服务器未存有图片, 返回一个 key 用于客户端上传
*/
class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray
override fun decode() {
this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))
uKey = this.input.readBytes(128)
}
}
/**
* 服务器已经存有这个图片
*/
class ServerTryGetImageIDFailedPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
override fun decode() {
}
}
\ No newline at end of file
......@@ -2,12 +2,15 @@
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.getRandomByteArray
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
import java.util.*
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* 向服务器检查是否可添加某人为好友
......@@ -20,15 +23,11 @@ class ClientCanAddFriendPacket(
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
val packetIdLast = getRandomByteArray(2)
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode() {
this.write(packetIdLast)//id, 2bytes
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
......@@ -38,7 +37,7 @@ class ClientCanAddFriendPacket(
}
@PacketId("00 A7")
class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(input) {
class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var state: State
enum class State {
......@@ -50,7 +49,8 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
override fun decode() {
val data = input.goto(0).readAllBytes()
input.readBytes()
val data = input.readRemainingBytes()
if (data.size == 99) {
state = State.ALREADY_ADDED
return
......@@ -61,13 +61,13 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
0x99u -> State.ALREADY_ADDED
0x03u,
0x04u -> State.FAILED
else -> throw IllegalArgumentException(Arrays.toString(data))
else -> throw IllegalArgumentException(data.contentToString())
}
}
@PacketId("00 A7")
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerCanAddFriendResponsePacket {
return ServerCanAddFriendResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
......@@ -84,15 +84,11 @@ class ClientAddFriendPacket(
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
val packetIdLast = getRandomByteArray(2)
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode() {
this.write(packetIdLast)//id, 2bytes
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
......@@ -104,17 +100,17 @@ class ClientAddFriendPacket(
}
class ServerAddFriendResponsePacket(input: DataInputStream) : ServerAddContactResponsePacket(input)
class ServerAddFriendResponsePacket(input: ByteReadPacket) : ServerAddContactResponsePacket(input)
class ServerAddGroupResponsePacket(input: DataInputStream) : ServerAddContactResponsePacket(input)
class ServerAddGroupResponsePacket(input: ByteReadPacket) : ServerAddContactResponsePacket(input)
/**
* 添加好友/群的回复
*/
abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPacket(input) {
abstract class ServerAddContactResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
class Raw(input: DataInputStream) : ServerPacket(input) {
class Raw(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() {
......@@ -125,7 +121,7 @@ abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPa
TODO()
}
class Encrypted(input: DataInputStream) : ServerPacket(input) {
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey))
}
}
......
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.*
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toByteArray
import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
import java.io.DataInputStream
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.*
/**
* @author Him188moe
*/
@PacketId("00 CD")
class ClientSendFriendMessagePacket(
private val botQQ: Long,
......@@ -17,8 +17,8 @@ class ClientSendFriendMessagePacket(
private val sessionKey: ByteArray,
private val message: MessageChain
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(botQQ)
this.writeHex(TIMProtocol.fixVer2)
......@@ -30,7 +30,7 @@ class ClientSendFriendMessagePacket(
writeHex("37 0F")//TIM最新: 38 03
writeQQ(botQQ)
writeQQ(targetQQ)
write(md5(dataEncode { md5Key -> md5Key.writeQQ(targetQQ); md5Key.write(sessionKey) }))
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
writeHex("00 0B")
writeRandom(2)
writeTime()
......@@ -54,11 +54,11 @@ class ClientSendFriendMessagePacket(
writeHex(TIMProtocol.messageConst1)//... 85 E9 BB 91
writeZero(2)
write(message.toByteArray())
writePacket(message.toPacket())
/*
//Plain text
val bytes = message.toByteArray()
val bytes = event.toPacket()
it.writeByte(0x01)
it.writeShort(bytes.size + 3)
it.writeByte(0x01)
......@@ -69,4 +69,4 @@ class ClientSendFriendMessagePacket(
}
@PacketId("00 CD")
class ServerSendFriendMessageResponsePacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerSendFriendMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toByteArray
import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
/**
* @author Him188moe
*/
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.*
@PacketId("00 02")
class ClientSendGroupMessagePacket(
private val botQQ: Long,
......@@ -18,39 +18,35 @@ class ClientSendGroupMessagePacket(
private val sessionKey: ByteArray,
private val message: MessageChain
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(botQQ)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
val bytes = message.toByteArray()
writeByte(0x2A)
writeGroup(groupId)
writeLVByteArray(dataEncode { child ->
child.writeHex("00 01 01")
child.writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
writeLVPacket {
writeHex("00 01 01")
writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
child.writeTime()
child.writeRandom(4)
child.writeHex("00 00 00 00 09 00 86")
child.writeHex(TIMProtocol.messageConst1)
child.writeZero(2)
writeTime()
writeRandom(4)
writeHex("00 00 00 00 09 00 86")
writeHex(TIMProtocol.messageConst1)
writeZero(2)
//messages
child.write(bytes)
})
writePacket(message.toPacket())
}
/*it.writeByte(0x01)
it.writeShort(bytes.size + 3)
it.writeByte(0x01)
it.writeShort(bytes.size)
it.write(bytes)*/
println(toByteArray().toUHexString())
}
}
}
@PacketId("00 02")
class ServerSendGroupMessageResponsePacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerSendGroupMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
......@@ -2,32 +2,32 @@
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.ClientLoginStatus
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.*
/**
* 改变在线状态: "我在线上", "隐身" 等
*
* @author Him188moe
*/
@PacketId("00 EC")
class ClientChangeOnlineStatusPacket(
private val qq: Long,
private val sessionKey: ByteArray,
private val loginStatus: ClientLoginStatus
private val loginStatus: OnlineStatus
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
writeHex("01 00")
writeByte(loginStatus.id.toInt())
writeUByte(loginStatus.id)
writeHex("00 01 00 01 00 04 00 00 00 00")
}
}
......
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.hexToBytes
import java.io.DataOutputStream
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.*
/**
* Password submission (0836_622)
*
* @author Him188moe
*/
@PacketId("08 36 31 03")
@Tested
......@@ -21,10 +20,11 @@ class ClientPasswordSubmissionPacket(
private val loginTime: Int,
private val loginIP: String,
private val privateKey: ByteArray,//16 random by client
private val token0825: ByteArray//56 from server
private val token0825: ByteArray,//56 from server
private val randomDeviceName: Boolean
) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.passwordSubmissionTLV1)
......@@ -36,7 +36,7 @@ class ClientPasswordSubmissionPacket(
//TODO shareKey 极大可能为 publicKey, key0836 计算得到
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
writePart1(qq, password, loginTime, loginIP, privateKey, token0825)
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, randomDeviceName)
writePart2()
}
}
......@@ -46,16 +46,13 @@ class ClientPasswordSubmissionPacket(
//但为简化处理, 特固定这个 id
@PacketId("08 36 31 04")
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
@PacketId("08 36 31 05")
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, null)
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
@PacketId("08 36 31 06")
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
open class ClientLoginResendPacket constructor(
......@@ -66,9 +63,10 @@ open class ClientLoginResendPacket constructor(
private val privateKey: ByteArray,
private val token0825: ByteArray,
private val token00BA: ByteArray,
private val tlv0006: ByteArray? = null
private val randomDeviceName: Boolean = false,
private val tlv0006: IoBuffer? = null
) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.passwordSubmissionTLV1)
......@@ -79,13 +77,14 @@ open class ClientLoginResendPacket constructor(
this.writeHex(TIMProtocol.key0836)//16
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, tlv0006)
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006)
writeHex("01 10") //tag
writeHex("00 3C")//length
writeHex("00 01")//tag
writeHex("00 38")//length
write(token00BA)//value
writeHex("01 10")
writeHex("00 3C")
writeHex("00 01")
writeHex("00 38")
writeFully(token00BA)
writePart2()
}
......@@ -93,32 +92,40 @@ open class ClientLoginResendPacket constructor(
}
/**
* @author Him188moe
*/
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) {
private fun BytePacketBuilder.writePart1(
qq: Long,
password: String,
loginTime: Int,
loginIP: String,
privateKey: ByteArray,
token0825: ByteArray,
randomDeviceName: Boolean,
tlv0006: IoBuffer? = null
) {
//this.writeInt(System.currentTimeMillis().toInt())
this.writeHex("01 12")//tag
this.writeHex("00 38")//length
this.write(token0825)//length
this.writeFully(token0825)//length
this.writeHex("03 0F")//tag
this.writeDeviceName(false)
this.writeDeviceName(randomDeviceName)
this.writeHex("00 05 00 06 00 02")
this.writeQQ(qq)
this.writeHex("00 06")//tag
this.writeHex("00 78")//length
if (tlv0006 != null) {
this.write(tlv0006)
MiraiLogger.logDebug("tlv0006!=null")
this.writeFully(tlv0006)
} else {
MiraiLogger.logDebug("tlv0006==null")
this.writeTLV0006(qq, password, loginTime, loginIP, privateKey)
}
//fix
this.writeHex(TIMProtocol.passwordSubmissionTLV2)
this.writeHex("00 1A")//tag
this.writeHex("00 40")//length
this.write(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), privateKey))
this.writeFully(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), privateKey))
this.writeHex(TIMProtocol.constantData1)
this.writeHex(TIMProtocol.constantData2)
this.writeQQ(qq)
......@@ -133,7 +140,7 @@ private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: I
}
private fun DataOutputStream.writePart2() {
private fun BytePacketBuilder.writePart2() {
this.writeHex("03 12")//tag
this.writeHex("00 05")//length
......@@ -161,9 +168,8 @@ private fun DataOutputStream.writePart2() {
//value
this.writeHex("E9 AA 2B 4D 26 4C 76 18 FE 59 D5 A9 82 6A 0C 04 B4 49 50 D7 9B B1 FE 5D 97 54 8D 82 F3 22 C2 48 B9 C9 22 69 CA 78 AD 3E 2D E9 C9 DF A8 9E 7D 8C 8D 6B DF 4C D7 34 D0 D3")
this.writeHex("00 14")//length
writeCRC32()
this.writeHex("00 14")
this.writeCRC32()
}
package net.mamoe.mirai.network.protocol.tim.packet.login
/**
* @author Him188moe
*/
enum class LoginState {
enum class LoginResult {
/**
* 登录成功
*/
......@@ -39,6 +36,11 @@ enum class LoginState {
*/
UNKNOWN,
/**
* 未知. 更换服务器或等几分钟再登录可能解决.
*/
INTERNAL_ERROR,
/**
* 超时
*/
......
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import java.io.DataInputStream
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* SKey 用于 http api
*
* @author Him188moe
*/
@PacketId("00 1D")
class ClientSKeyRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(qq)
writeHex(TIMProtocol.fixVer2)
encryptAndWrite(sessionKey) {
writeHex("33 00 05 00 08 74 2E 71 71 2E 63 6F 6D 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 0C 6A 75 62 61 6F 2E 71 71 2E 63 6F 6D 00 09 6B 65 2E 71 71 2E 63 6F 6D")
}
}
}
/**
* @author Him188moe
*/
@PacketId("00 1D")
class ClientSKeyRefreshmentRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.encryptAndWrite(sessionKey) {
writeHex("33 00 05 00 08 74 2E 71 71 2E 63 6F 6D 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 0C 6A 75 62 61 6F 2E 71 71 2E 63 6F 6D 00 09 6B 65 2E 71 71 2E 63 6F 6D")
......@@ -45,18 +48,16 @@ class ClientSKeyRefreshmentRequestPacket(
}
}
/**
* @author Him188moe
*/
class ServerSKeyResponsePacket(input: DataInputStream) : ServerPacket(input) {
class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var sKey: String
override fun decode() {
this.sKey = String(this.input.goto(4).readNBytes(10))
override fun decode() = with(input) {
discardExact(4)
sKey = this.readString(10)//todo test
MiraiLogger.logDebug("SKey=$sKey")
}
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket = ServerSKeyResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
sealed class ServerLoginResponsePacket(input: ByteReadPacket) : ServerPacket(input)
class ServerLoginResponseFailedPacket(val loginResult: LoginResult, input: ByteReadPacket) : ServerLoginResponsePacket(input)
/**
* 服务器进行加密后返回 privateKey
*
* @author NaturalHG
*/
@PacketId("08 36 31 03")
class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket, val flag: Flag) : ServerLoginResponsePacket(input) {
enum class Flag {
`08 36 31 03`,
OTHER,
}
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)//22
this.input.discardExact(4)//00 06 00 78
tlv0006 = this.input.readIoBuffer(0x78)
when (flag) {
Flag.`08 36 31 03` -> {//TODO 在解析时分类而不是在这里
this.input.discardExact(8)//01 10 00 3C 00 01 00 38
tokenUnknown = this.input.readBytes(56)
//println(tokenUnknown!!.toUHexString())
}
Flag.OTHER -> {
//do nothing in this packet.
//[this.token] will be set in [BotNetworkHandler]
//token
}
}
}
class Encrypted(input: ByteReadPacket, private val flag: Flag) : ServerPacket(input) {
@Tested
fun decrypt(privateKey: ByteArray): ServerLoginResponseKeyExchangePacket = ServerLoginResponseKeyExchangePacket(this.decryptBy(TIMProtocol.shareKey, privateKey), flag).setId(this.idHex)
}
}
enum class Gender(val id: Boolean) {
MALE(false),
FEMALE(true);
}
/**
* @author NaturalHG
*/
class ServerLoginResponseSuccessPacket(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 (val flag = readBytes(2).toUHexString()) {
"01 07" -> 0
"00 33" -> 28
"01 10" -> 64
else -> throw IllegalStateException(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
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(privateKey: ByteArray): ServerLoginResponseSuccessPacket = ServerLoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).setId(this.idHex)
}
}
/**
* 收到这个包意味着需要验证码登录, 并且能得到验证码图片文件的一部分
*
* @author Him188moe
*/
class ServerLoginResponseVerificationCodeInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var verifyCodePart1: IoBuffer
lateinit var token00BA: ByteArray
var unknownBoolean: Boolean? = null
@Tested
override fun decode() {
this.input.discardExact(78)
//println(this.input.readRemainingBytes().toUHexString())
val verifyCodeLength = this.input.readShort()//2bytes
this.verifyCodePart1 = this.input.readIoBuffer(verifyCodeLength)
this.input.discardExact(1)
this.unknownBoolean = this.input.readByte().toInt() == 1
this.input.discardExact(this.input.remaining - 60)
this.token00BA = this.input.readBytes(40)
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket = ServerLoginResponseVerificationCodeInitPacket(this.decryptAsByteArray(TIMProtocol.shareKey).toReadPacket()).setId(this.idHex)
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.DataInputStream
/**
* Congratulations!
......@@ -10,4 +10,4 @@ import java.io.DataInputStream
* @author Him188moe
*/
@PacketId("00 EC")
class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerLoginSuccessPacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
import java.io.DataInputStream
import java.net.InetAddress
/**
* @author Him188moe
*/
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
@PacketId("08 28 04 34")
class ClientSessionRequestPacket(
private val qq: Long,
private val serverIp: String,
private val token38: ByteArray,
private val token88: ByteArray,
private val encryptionKey: ByteArray
private val token38: IoBuffer,
private val token88: IoBuffer,
private val encryptionKey: IoBuffer
) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A")
this.writeHex("00 38")
this.write(token38)
this.writeFully(token38)
this.encryptAndWrite(encryptionKey) {
writeHex("00 07 00 88")
write(token88)
writeFully(token88)
writeHex("00 0C 00 16 00 02 00 00 00 00 00 00 00 00 00 00")
writeIP(serverIp)
writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1
......@@ -56,44 +54,51 @@ class ClientSessionRequestPacket(
writeHex("68")
writeHex("00 00 00 00 00 2D 00 06 00 01")
writeIP(InetAddress.getLocalHost().hostAddress)//todo random to avoid being banned?
writeIP(localIpAddress())//todo random to avoid being banned?
}
}
}
/**
* @author Him188moe
*/
@PacketId("08 28 04 34")
class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) {
class ServerSessionKeyResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
lateinit var sessionKey: ByteArray
lateinit var tlv0105: ByteArray
override fun decode() {
when (dataLength) {
407 -> {
input.goto(25)
sessionKey = input.readNBytes(16)
lateinit var tlv0105: ByteReadPacket
@Tested
override fun decode() = with(input) {
when (val dataLength = remaining) {
407L -> {
input.discardExact(25)//todo test
sessionKey = input.readBytes(16)
}
439 -> {
input.goto(63)
sessionKey = input.readNBytes(16)
439L -> {
input.discardExact(63)
sessionKey = input.readBytes(16)
}
512,
527 -> {
input.goto(63)
sessionKey = input.readNBytes(16)
tlv0105 = dataEncode {
it.writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00")
input.goto(dataLength - 122)
it.write(input.readNBytes(56))
it.writeHex("00 40 02 02 03 3C 01 03 00 00")
input.goto(dataLength - 55)
it.write(input.readNBytes(56))
502L,//?
512L,
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
sessionKey = input.readBytes(16)
tlv0105 = buildPacket {
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)
writeFully(input.readIoBuffer(56))
writeHex("00 40 02 02 03 3C 01 03 00 00")
input.discardExact(11)
writeFully(input.readIoBuffer(56))
} //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
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
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
*/
}
else -> throw IllegalArgumentException(dataLength.toString())
......@@ -104,9 +109,8 @@ class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val d
}
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
fun decrypt(sessionResponseDecryptionKey: ByteArray): ServerSessionKeyResponsePacket = this.decryptAsByteArray(sessionResponseDecryptionKey).let {
ServerSessionKeyResponsePacket(it.dataInputStream(), it.size).setId(this.idHex)
}
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionResponseDecryptionKey: IoBuffer): ServerSessionKeyResponsePacket =
ServerSessionKeyResponsePacket(this.decryptBy(sessionResponseDecryptionKey)).setId(this.idHex)
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
import java.io.IOException
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* The packet received when logging in, used to redirect server address
......@@ -17,7 +21,7 @@ import java.io.IOException
*/
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@PacketId("08 25 31 01")
class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) {
class ServerTouchResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
var serverIP: String? = null
var loginTime: Int = 0
......@@ -30,28 +34,28 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
}
override fun decode() {
when (val id = input.readByte().toUByte().toInt()) {
0xFE -> {
input.skip(94)
serverIP = input.readIP()
override fun decode() = with(input) {
when (val id = readByte().toUByte().toInt()) {
0xFE -> {//todo 在 packet 解析时分类而不是在这里分类
discardExact(94)
serverIP = readIP()
}
0x00 -> {
input.skip(4)
token0825 = input.readNBytes(56)
input.skip(6)
discardExact(4)
token0825 = readBytes(56)
discardExact(6)
loginTime = input.readInt()
loginIP = input.readIP()
loginTime = readInt()
loginIP = readIP()
}
else -> {
throw IllegalStateException(arrayOf(id.toUByte()).toUByteArray().toUHexString())
throw IllegalStateException(id.toByte().toUHexString())
}
}
}
class Encrypted(private val type: Type, inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(private val type: Type, inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) {
Type.TYPE_08_25_31_02 -> TIMProtocol.redirectionKey.hexToBytes()
......@@ -61,16 +65,13 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
}
/**
* The packet to touch server, that is, to start the connection to the server.
* The packet to sendTouch server, that is, to start the connection to the server.
*
* @author Him188moe
*/
@PacketId("08 25 31 01")
class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() {
@Throws(IOException::class)
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.touchKey)
......@@ -94,13 +95,11 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
*/
@PacketId("08 25 31 02")
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.redirectionKey)
this.encryptAndWrite(TIMProtocol.redirectionKey) {
this.writeHex(TIMProtocol.constantData1)
this.writeHex(TIMProtocol.constantData2)
......
@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.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.Tested
import java.io.DataInputStream
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* 客户端请求验证码图片数据的第几部分
*/
@PacketId("00 BA 31")
class ClientVerificationCodeTransmissionRequestPacket(
private val packetId: Int,
private val packetIdLast: Int,//ubyte
private val qq: Long,
private val token0825: ByteArray,
private val verificationSequence: Int,
private val token00BA: ByteArray
) : ClientPacket() {
@Tested
override fun encode() {
this.writeByte(packetId)//part of packet id
override val idHex: String by lazy {
super.idHex + " " + packetIdLast.toByte().toUHexString()
}
@Tested
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.key00BA)
......@@ -27,13 +34,13 @@ class ClientVerificationCodeTransmissionRequestPacket(
writeHex("00 02 00 00 08 04 01 E0")
writeHex(TIMProtocol.constantData2)
writeHex("00 00 38")
write(token0825)
writeFully(token0825)
writeHex("01 03 00 19")
writeHex(TIMProtocol.publicKey)
writeHex("13 00 05 00 00 00 00")
writeByte(verificationSequence)
writeUByte(verificationSequence.toUByte())
writeHex("00 28")
write(token00BA)
writeFully(token00BA)
writeHex("00 10")
writeHex(TIMProtocol.key00BAFix)
}
......@@ -45,19 +52,21 @@ class ClientVerificationCodeTransmissionRequestPacket(
*/
@PacketId("00 BA 32")
class ClientVerificationCodeSubmitPacket(
private val packetIdLast: Int,
private val packetIdLast: Int,//ubyte
private val qq: Long,
private val token0825: ByteArray,
private val captcha: String,
private val verificationToken: ByteArray
private val verificationToken: IoBuffer
) : ClientPacket() {
init {
require(captcha.length == 4) { "captcha.length must == 4" }
}
override fun encode() {
this.writeByte(packetIdLast)//part of packet id
override val idHex: String by lazy {
super.idHex + " " + packetIdLast.toByte().toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.key00BA)
......@@ -65,25 +74,21 @@ class ClientVerificationCodeSubmitPacket(
writeHex("00 02 00 00 08 04 01 E0")
writeHex(TIMProtocol.constantData2)
writeHex("01 00 38")
write(token0825)
writeFully(token0825)
writeHex("01 03")
writeShort(25)
writeHex(TIMProtocol.publicKey)//25
writeHex("14 00 05 00 00 00 00 00 04")
write(captcha.toUpperCase().toByteArray())
writeStringUtf8(captcha.toUpperCase())
writeHex("00 38")
write(verificationToken)
writeFully(verificationToken)
writeShort(16)
writeHex(TIMProtocol.key00BAFix)//16
}
}
override fun getFixedId(): String {
return this.idHex + " " + packetIdLast
}
}
/**
......@@ -95,9 +100,11 @@ class ClientVerificationCodeRefreshPacket(
private val qq: Long,
private val token0825: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeByte(packetIdLast)//part of packet id
override val idHex: String by lazy {
super.idHex + " " + packetIdLast.toByte().toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.key00BA)
......@@ -105,24 +112,20 @@ class ClientVerificationCodeRefreshPacket(
writeHex("00 02 00 00 08 04 01 E0")
writeHex(TIMProtocol.constantData2)
writeHex("00 00 38")
write(token0825)
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 fun getFixedId(): String {
return this.idHex + " " + packetIdLast
}
}
/**
* 验证码输入错误, 同时也会给一部分验证码
*/
@PacketId("00 BA 32")
class ServerCaptchaWrongPacket(input: DataInputStream, dataSize: Int, packetId: ByteArray) : ServerCaptchaTransmissionPacket(input, dataSize, packetId)
class ServerCaptchaWrongPacket(input: ByteReadPacket, packetIdLast: Int) : ServerCaptchaTransmissionPacket(input, packetIdLast)
/**
* 服务器发送验证码图片文件一部分过来
......@@ -130,38 +133,34 @@ class ServerCaptchaWrongPacket(input: DataInputStream, dataSize: Int, packetId:
* @author Him188moe
*/
@PacketId("00 BA 31")
open class ServerCaptchaTransmissionPacket(input: DataInputStream, private val dataSize: Int, private val packetId: ByteArray) : ServerCaptchaPacket(input) {
open class ServerCaptchaTransmissionPacket(input: ByteReadPacket, val packetIdLast: Int) : ServerCaptchaPacket(input) {
lateinit var captchaSectionN: ByteArray
lateinit var verificationToken: ByteArray//56bytes
lateinit var captchaSectionN: IoBuffer
lateinit var verificationToken: IoBuffer//56bytes
var transmissionCompleted: Boolean = false//验证码是否已经传输完成
lateinit var token00BA: ByteArray//40 bytes
var packetIdLast: Int = 0
override fun decode() {
this.verificationToken = this.input.readNBytesAt(10, 56)
override fun decode() = with(input) {
input.discardExact(10)//13 00 05 01 00 00 01 23 00 38
verificationToken = readIoBuffer(56)
val length = this.input.readShortAt(66)
this.captchaSectionN = this.input.readNBytes(length)
val length = readShort()
captchaSectionN = readIoBuffer(length)
this.input.skip(1)
val byte = this.input.readByteAt(69 + length).toInt()
this.transmissionCompleted = byte == 0
this.token00BA = this.input.readNBytesAt(dataSize - 56 - 2, 40)
this.packetIdLast = packetId[3].toInt()
}
discardExact(1)
val byte = readByte().toInt()
transmissionCompleted = byte == 0
override fun getFixedId(): String {
return this.idHex + " " + packetIdLast
discardExact(remaining - 56 - 2)
token00BA = readBytes(40)
}
}
/*
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()
ServerVerificationCodeTransmissionPacket(data.dataInputStream(), data.size, "00 BA 31 01".hexToBytes()).let {
ServerVerificationCodeTransmissionPacket(data.toReadPacket(), data.size, "00 BA 31 01".hexToBytes()).let {
it.dataDecode()
println(it.toString())
}
......@@ -173,35 +172,31 @@ fun main() {
* @author Him188moe
*/
@PacketId("00 BA 32")
class ServerCaptchaCorrectPacket(input: DataInputStream) : ServerCaptchaPacket(input) {
class ServerCaptchaCorrectPacket(input: ByteReadPacket) : ServerCaptchaPacket(input) {
lateinit var token00BA: ByteArray//56 bytes
override fun decode() {
token00BA = this.input.readNBytesAt(10, 56)
override fun decode() = with(input) {
discardExact(10)//14 00 05 00 00 00 00 00 00 38
token00BA = readBytes(56)
}
}
abstract class ServerCaptchaPacket(input: DataInputStream) : ServerPacket(input) {
abstract class ServerCaptchaPacket(input: ByteReadPacket) : ServerPacket(input) {
@PacketId("00 BA")
class Encrypted(input: DataInputStream, private val id: String) : ServerPacket(input) {
class Encrypted(input: ByteReadPacket, override var idHex: String) : ServerPacket(input) {
fun decrypt(): ServerCaptchaPacket {
val data = this.decryptAsByteArray(TIMProtocol.key00BA)
if (id.startsWith("00 BA 32")) {
if (idHex.startsWith("00 BA 32")) {
return when (data.size) {
66,
95 -> ServerCaptchaCorrectPacket(data.dataInputStream())
//66 -> ServerVerificationCodeUnknownPacket(data.dataInputStream())
else -> return ServerCaptchaWrongPacket(data.dataInputStream(), data.size, this.input.readNBytesAt(3, 4))
95 -> ServerCaptchaCorrectPacket(data.toReadPacket())
//66 -> ServerVerificationCodeUnknownPacket(data.toReadPacket())
else -> return ServerCaptchaWrongPacket(data.toReadPacket(), idHex.substringAfterLast(" ").toInt(16))
}.setId(this.idHex)
}
return ServerCaptchaTransmissionPacket(data.dataInputStream(), data.size, this.input.readNBytesAt(3, 4)).setId(this.idHex)
return ServerCaptchaTransmissionPacket(data.toReadPacket(), idHex.substringAfterLast(" ").toInt(16)).setId(this.idHex)
}
override fun getFixedId(): String = this.getFixedId(id)
}
}
package net.mamoe.mirai.utils
/**
* @author Him188moe
*/
data class BotAccount(
val qqNumber: Long,//实际上是 UInt
val password: String//todo 不保存 password?
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlin.jvm.JvmOverloads
@JvmOverloads
fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
}
return@joinToString ret
}
@JvmOverloads
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator)
fun ByteArray.stringOf(): String = String(this)
//@JvmSynthetic TODO 等待 kotlin 修复 bug 后添加这个注解
@JvmOverloads
fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
}
return@joinToString ret
}
fun ByteArray.toReadPacket() = ByteReadPacket(this)
fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().run(t)
fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
fun ByteReadPacket.readRemainingBytes(
n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
fun ByteReadPacket.readIoBuffer(
n: Int = remaining.toInt()//not that safe but adequate
): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) }
fun ByteReadPacket.readIoBuffer(n: Number) = this.readIoBuffer(n.toInt())
//必须消耗完 packet
fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {//TODO 优化
discardExact(3)
val idHex = readInt().toUHexString(" ")
discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8
return when (idHex) {
"08 25 31 01" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, this)
"08 25 31 02" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, this)
"08 36 31 03", "08 36 31 04", "08 36 31 05", "08 36 31 06" -> {
when (size) {
271, 207 -> return ServerLoginResponseKeyExchangePacket.Encrypted(this, when (idHex) {
"08 36 31 03" -> ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`
else -> ServerLoginResponseKeyExchangePacket.Flag.OTHER
}).setId(idHex)
871 -> return ServerLoginResponseVerificationCodeInitPacket.Encrypted(this).setId(idHex)
}
if (size > 700) {
return ServerLoginResponseSuccessPacket.Encrypted(this).setId(idHex)
}
println(size)
return ServerLoginResponseFailedPacket(when (size) {
135 -> {//包数据错误. 目前怀疑是 tlv0006
this.readRemainingBytes().cutTail(1).decryptBy(TIMProtocol.shareKey).read {
discardExact(51)
MiraiLogger.logError("Internal logError: " + readLVString())//抱歉,请重新输入密码。
}
LoginResult.INTERNAL_ERROR
} //可能是包数据错了. 账号没有被ban, 用TIM官方可以登录
319, 351 -> LoginResult.WRONG_PASSWORD
//135 -> LoginState.RETYPE_PASSWORD
63, 279 -> LoginResult.BLOCKED
263 -> LoginResult.UNKNOWN_QQ_NUMBER
551, 487 -> LoginResult.DEVICE_LOCK
343, 359 -> LoginResult.TAKEN_BACK
else -> LoginResult.UNKNOWN
/*
//unknown
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown logError)")
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown logError)")
else -> throw IllegalArgumentException(bytes.size.toString())*/
}, this).setId(idHex)
}
"08 28 04 34" -> ServerSessionKeyResponsePacket.Encrypted(this)
else -> when (idHex.substring(0, 5)) {
"00 EC" -> ServerLoginSuccessPacket(this)
"00 1D" -> ServerSKeyResponsePacket.Encrypted(this)
"00 5C" -> ServerAccountInfoResponsePacket.Encrypted(this)
"00 58" -> ServerHeartbeatResponsePacket(this)
"00 BA" -> ServerCaptchaPacket.Encrypted(this, idHex)
"00 CE", "00 17" -> ServerEventPacket.Raw.Encrypted(this, idHex.hexToBytes())
"00 81" -> ServerFieldOnlineStatusChangedPacket.Encrypted(this)
"00 CD" -> ServerSendFriendMessageResponsePacket(this)
"00 02" -> ServerSendGroupMessageResponsePacket(this)
"00 A7" -> ServerCanAddFriendResponsePacket(this)
"03 88" -> ServerTryGetImageIDResponsePacket.Encrypted(this)
else -> UnknownServerPacket.Encrypted(this)
}
}.setId(idHex)
}
fun ByteReadPacket.readIP(): String = buildString(4 + 3) {
repeat(4) {
val byte = readUByte()
this.append(byte.toString())
if (it != 3) this.append(".")
}
}
fun ByteReadPacket.readLVString(): String = String(this.readLVByteArray())
fun ByteReadPacket.readLVByteArray(): ByteArray = this.readBytes(this.readShort().toInt())
fun ByteReadPacket.readTLVMap(expectingEOF: Boolean = false): Map<Int, ByteArray> {
val map = mutableMapOf<Int, ByteArray>()
var type: UByte
try {
type = readUByte()
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
while (type != UByte.MAX_VALUE) {
map[type.toInt()] = this.readLVByteArray()
try {
type = readUByte()
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
}
return map
}
fun Map<Int, ByteArray>.printTLVMap(name: String) = debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() })
fun ByteReadPacket.readString(length: Number): String = String(this.readBytes(length.toInt()))
private const val TRUE_BYTE_VALUE: Byte = 1
fun ByteReadPacket.readBoolean(): Boolean = this.readByte() == TRUE_BYTE_VALUE
fun ByteReadPacket.readLVNumber(): Number {
return when (this.readShort().toInt()) {
1 -> this.readByte()
2 -> this.readShort()
4 -> this.readInt()
8 -> this.readLong()
else -> throw UnsupportedOperationException()
}
}
//添加@JvmSynthetic 导致 idea 无法检查这个文件的错误
//@JvmSynthetic
@Deprecated("Low efficiency", ReplaceWith(""))
fun ByteReadPacket.gotoWhere(matcher: UByteArray): ByteReadPacket {
return this.gotoWhere(matcher.toByteArray())
}
/**
* 去往下一个含这些连续字节的位置
*/
@Deprecated("Low efficiency", ReplaceWith(""))
fun ByteReadPacket.gotoWhere(matcher: ByteArray): ByteReadPacket {
require(matcher.isNotEmpty())
loop@
do {
val byte = this.readByte()
if (byte == matcher[0]) {
//todo mark here
for (i in 1 until matcher.size) {
val b = this.readByte()
if (b != matcher[i]) {
continue@loop //todo goto mark
}
}
return this
}
} while (true)
}
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
private const val GTK_BASE_VALUE: Int = 5381
@ExperimentalStdlibApi
internal fun getGTK(sKey: String): Int {
var value = GTK_BASE_VALUE
for (c in sKey.toCharArray()) {
value += (value shl 5) + c.toInt()
}
value = value and Int.MAX_VALUE
return value
}
@Tested
fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
fun BytePacketBuilder.writeCRC32(key: ByteArray) {
writeFully(key)//key
writeInt(crc32(key))
}
fun md5(str: String): ByteArray = md5(str.toByteArray())
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
/**
* 让用户处理验证码
*
* @return 用户输入得到的验证码. 非 null 时一定 `length==4`.
*/
internal expect suspend fun solveCaptcha(captchaBuffer: IoBuffer): String?
......@@ -2,7 +2,5 @@ package net.mamoe.mirai.utils
import net.mamoe.mirai.contact.Contact
/**
* @author Him188moe
*/
class ContactList<C : Contact> : MutableMap<Long, C> by mutableMapOf()
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
internal object DebugLogger : MiraiLogger by PlatformLogger("Packet Debug")
internal fun debugPrintln(any: Any?) = DebugLogger.logPurple(any)
@Deprecated("Debugging Warning", ReplaceWith(""))
internal fun ByteArray.debugPrint(name: String): ByteArray {
DebugLogger.logPurple(name + "=" + this.toUHexString())
return this
}
@Deprecated("Debugging Warning", ReplaceWith(""))
internal fun IoBuffer.debugPrint(name: String): IoBuffer {
val readBytes = this.readBytes()
DebugLogger.logPurple(name + "=" + readBytes.toUHexString())
return readBytes.toIoBuffer()
}
@Deprecated("Debugging Warning", ReplaceWith(""))
internal fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.logPurple("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
}
package net.mamoe.mirai.utils
inline fun <T> MutableIterable<T>.removeIfInlined(predicate: (T) -> Boolean) = iterator().removeIfInlined(predicate)
inline fun <T> MutableIterator<T>.removeIfInlined(predicate: (T) -> Boolean) {
while (this.hasNext()) {
if (predicate(this.next())) {
this.remove()
}
}
}
\ No newline at end of file
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerTouchResponsePacket
class LoginConfiguration {
/**
* 等待 [ServerTouchResponsePacket] 的时间
*/
var touchTimeoutMillis: Long = 2000
var randomDeviceName: Boolean = false
companion object {
val Default = LoginConfiguration()
}
}
package net.mamoe.mirai.utils
import kotlin.jvm.JvmOverloads
interface MiraiLogger {
companion object : MiraiLogger by PlatformLogger("[TOP Level]")
var identity: String?
fun logInfo(any: Any?) = log(any)
fun log(e: Throwable)
fun log(any: Any?)
fun logError(any: Any?)
fun logDebug(any: Any?)
fun logCyan(any: Any?)
fun logPurple(any: Any?)
fun logGreen(any: Any?)
fun logBlue(any: Any?)
}
expect class PlatformLogger @JvmOverloads constructor(identity: String? = null) : MiraiLogger
fun Throwable.log() = MiraiLogger.log(this)
fun Throwable.printStacktrace() = this.log()
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
/**
......@@ -7,12 +8,21 @@ package net.mamoe.mirai.utils
* @author Him188moe
* @see net.mamoe.mirai.network.protocol.tim.packet.login.ClientChangeOnlineStatusPacket
*/
enum class ClientLoginStatus(
// TODO: 2019/8/31 add more ClientLoginStatus
enum class OnlineStatus(
val id: UByte//1 ubyte
) {
/**
* 我在线上
*/
ONLINE(0x0Au)
ONLINE(0x0Au),
/**
* 忙碌
*/
BUSY(0x32u);
companion object {
fun ofId(id: UByte): OnlineStatus = values().first { it.id == id }
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import kotlin.random.Random
import kotlin.random.nextInt
fun BytePacketBuilder.writeZero(count: Int) = repeat(count) { this.writeByte(0) }
fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt())
fun BytePacketBuilder.writeGroup(groupIdOrGroupNumber: Long) = this.writeFully(groupIdOrGroupNumber.toUInt().toByteArray())
fun BytePacketBuilder.writeLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray)
}
fun BytePacketBuilder.writeLVPacket(packet: ByteReadPacket) {
this.writeShort(packet.remaining.toShort())
this.writePacket(packet)
}
fun BytePacketBuilder.writeLVPacket(builder: BytePacketBuilder.() -> Unit) = this.writeLVPacket(BytePacketBuilder().apply(builder).use { it.build() })
@Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVString(str: String) = this.writeLVByteArray(str.toByteArray())
@Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVHex(hex: String) = this.writeLVByteArray(hex.hexToBytes())
fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray())
fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt())
fun BytePacketBuilder.writeHex(uHex: String) = this.writeFully(uHex.hexToUBytes())
fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.readBytes(), encoder)
fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = writeFully(TEA.encrypt(BytePacketBuilder().apply(encoder).use { it.build().readBytes() }, key))
fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
fun BytePacketBuilder.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) {
val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
this.encryptAndWrite(secondMD5) {
writeRandom(4)
writeHex("00 02")
writeQQ(qq)
writeHex(TIMProtocol.constantData2)
writeHex("00 00 01")
writeFully(firstMD5)
writeInt(loginTime)
writeByte(0)
writeZero(4 * 3)
writeIP(loginIP)
writeZero(8)
writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
writeFully(privateKey)
}
}
@Tested
fun BytePacketBuilder.writeDeviceName(random: Boolean) {
val deviceName: String = if (random) {
"DESKTOP-" + String(ByteArray(7) {
(if (Random.nextBoolean()) Random.nextInt('A'.toInt()..'Z'.toInt())
else Random.nextInt('1'.toInt()..'9'.toInt())).toByte()
})
} else {
deviceName
}
this.writeShort((deviceName.length + 2).toShort())
this.writeShort(deviceName.length.toShort())
this.writeStringUtf8(deviceName)//TODO TEST?
}
\ No newline at end of file
package net.mamoe.mirai.utils
/**
* 时间戳
*/
expect val currentTime: Long
/**
* 设备名
*/
expect val deviceName: String
/**
* CRC32 算法
*/
expect fun crc32(key: ByteArray): Int
/**
* MD5 算法
*
* @return 16 bytes
*/
expect fun md5(byteArray: ByteArray): ByteArray
/**
* Hostname 解析 IP 地址
*/
expect fun solveIpAddress(hostname: String): String
/**
* Localhost 解析
*/
expect fun localIpAddress(): String
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.errors.IOException
expect class MiraiDatagramChannel(serverHost: String, serverPort: Short) : Closeable {
suspend fun read(buffer: IoBuffer): Int
suspend fun send(buffer: IoBuffer): Int
val isOpen: Boolean
}
expect class ClosedChannelException : IOException
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import kotlin.jvm.JvmStatic
expect object TEA {
internal fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray
@JvmStatic
fun encrypt(source: ByteArray, key: ByteArray): ByteArray
@JvmStatic
fun decrypt(source: ByteArray, key: ByteArray): ByteArray
@JvmStatic
fun decrypt(source: ByteArray, key: IoBuffer): ByteArray
@JvmStatic
fun decrypt(source: ByteArray, keyHex: String): ByteArray
}
fun ByteArray.decryptBy(key: ByteArray): ByteArray = TEA.decrypt(this, key)
fun ByteArray.decryptBy(key: IoBuffer): ByteArray = TEA.decrypt(this, key)
fun ByteArray.decryptBy(key: String): ByteArray = TEA.decrypt(this, key)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import kotlin.jvm.Synchronized
import kotlin.random.Random
import kotlin.random.nextInt
/**
* 255 -> 00 00 00 FF
*/
fun Int.toByteArray(): ByteArray = byteArrayOf(
(this.ushr(24) and 0xFF).toByte(),
(this.ushr(16) and 0xFF).toByte(),
(this.ushr(8) and 0xFF).toByte(),
(this.ushr(0) and 0xFF).toByte()
)
/**
* 255u -> 00 00 00 FF
*/
fun UInt.toByteArray(): ByteArray = byteArrayOf(
(this.shr(24) and 255u).toByte(),
(this.shr(16) and 255u).toByte(),
(this.shr(8) and 255u).toByte(),
(this.shr(0) and 255u).toByte()
)
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
fun Byte.toUHexString(): String = this.toUByte().toString(16).toUpperCase()
fun String.hexToBytes(): ByteArray = HexCache.hexToBytes(this)
fun String.hexToUBytes(): UByteArray = HexCache.hexToUBytes(this)
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
fun ByteArray.toUInt(): UInt = this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0)
fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it }
internal object HexCache {
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
@Synchronized
internal fun hexToBytes(hex: String): ByteArray = hex.hashCode().let { id ->
if (hexToByteArrayCacheMap.containsKey(id)) {
return hexToByteArrayCacheMap[id]!!.copyOf()
} else {
hexToUBytes(hex).toByteArray().let {
hexToByteArrayCacheMap[id] = it.copyOf()
return it
}
}
}
internal fun hexToUBytes(hex: String): UByteArray =
hex.split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
.map { value -> value.trim { it <= ' ' } }
.map { s -> s.toUByte(16) }
.toUByteArray()
}
\ No newline at end of file
package net.mamoe.mirai.utils
//todo
\ No newline at end of file
......@@ -3,11 +3,10 @@
package net.mamoe.mirai.utils
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.IOException
import java.io.InputStream
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlin.experimental.or
import kotlin.jvm.JvmName
/**
* Tool class for VarInt or VarLong operations.
......@@ -23,7 +22,7 @@ fun encodeZigZag32(signedInt: Int): Long {
}
@JvmSynthetic
//@JvmSynthetic
fun decodeZigZag32(uint: UInt): Int {
return decodeZigZag32(uint.toLong())
}
......@@ -41,57 +40,47 @@ fun decodeZigZag64(signedLong: Long): Long {
}
@Throws(IOException::class)
fun DataInputStream.readVarInt(): Int {
fun ByteReadPacket.readVarInt(): Int {
return decodeZigZag32(this.readUnsignedVarInt())
}
@Throws(IOException::class)
fun DataInputStream.readUnsignedVarInt(): UInt {
fun ByteReadPacket.readUnsignedVarInt(): UInt {
return read(this, 5).toUInt()
}
@Throws(IOException::class)
fun DataInputStream.readVarLong(): Long {
fun ByteReadPacket.readVarLong(): Long {
return decodeZigZag64(readUnsignedVarLong().toLong())
}
@Throws(IOException::class)
fun DataInputStream.readUnsignedVarLong(): ULong {
fun ByteReadPacket.readUnsignedVarLong(): ULong {
return read(this, 10).toULong()
}
@Throws(IOException::class)
fun DataOutputStream.writeVarInt(signedInt: Int) {
fun BytePacketBuilder.writeVarInt(signedInt: Int) {
this.writeUVarInt(encodeZigZag32(signedInt))
}
@Throws(IOException::class)
fun DataOutputStream.writeUVarInt(uint: UInt) {
fun BytePacketBuilder.writeUVarInt(uint: UInt) {
return writeUVarInt(uint.toLong())
}
@Throws(IOException::class)
fun DataOutputStream.writeUVarInt(uint: Long) {
fun BytePacketBuilder.writeUVarInt(uint: Long) {
this.write0(uint)
}
@Throws(IOException::class)
fun DataOutputStream.writeVarLong(signedLong: Long) {
fun BytePacketBuilder.writeVarLong(signedLong: Long) {
this.writeUVarLong(encodeZigZag64(signedLong))
}
@Throws(IOException::class)
fun DataOutputStream.writeUVarLong(ulong: Long) {
fun BytePacketBuilder.writeUVarLong(ulong: Long) {
this.write0(ulong)
}
@Throws(IOException::class)
private fun DataOutputStream.write0(long: Long) {
private fun BytePacketBuilder.write0(long: Long) {
var value = long
do {
var temp = (value and 127).toByte()
......@@ -99,34 +88,19 @@ private fun DataOutputStream.write0(long: Long) {
if (value != 0L) {
temp = temp or 128.toByte()
}
this.writeByte(temp.toInt())
this.writeByte(temp)
} while (value != 0L)
}
@Throws(IOException::class)
private fun read(stream: DataInputStream, maxSize: Int): Long {
private fun read(stream: ByteReadPacket, maxSize: Int): Long {
var value: Long = 0
var size = 0
var b = stream.readByte().toInt()
while (b and 0x80 == 0x80) {
value = value or ((b and 0x7F).toLong() shl size++ * 7)
require(size < maxSize) { "VarLong too big" }
require(size < maxSize) { "VarLong too bigger(expecting maxSize=$maxSize)" }
b = stream.readByte().toInt()
}
return value or ((b and 0x7F).toLong() shl size * 7)
}
\ No newline at end of file
@Throws(IOException::class)
private fun read(stream: InputStream, maxSize: Int): Long {
var value: Long = 0
var size = 0
var b = stream.read()
while (b and 0x80 == 0x80) {
value = value or ((b and 0x7F).toLong() shl size++ * 7)
require(size < maxSize) { "VarLong too big" }
b = stream.read()
}
return value or ((b and 0x7F).toLong() shl size * 7)
}
\ No newline at end of file
package net.mamoe.mirai.event.internal
import net.mamoe.mirai.event.Event
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.isSuperclassOf
@Suppress("UNCHECKED_CAST")
internal actual inline fun <E : Event> loopAllListeners(clazz: KClass<E>, consumer: (EventListeners<in E>) -> Unit) {
clazz.allSuperclasses.forEach {
if (Event::class.isSuperclassOf(it)) {
consumer((it as KClass<out Event>).listeners as EventListeners<in E>)
}
}
}
\ No newline at end of file
package net.mamoe.mirai.message
/**
* @author LamGC
*/
@Suppress("EnumEntryName", "unused", "SpellCheckingInspection")
enum class FaceID constructor(val id: Int) {
unknown(0xff),
// TODO: 2019/9/1 添加更多表情
jingya(0),
piezui(1),
se(2),
fadai(3),
deyi(4),
liulei(5),
haixiu(6),
bizui(7),
shui(8),
daku(9),
ganga(10),
fanu(11),
tiaopi(12),
ciya(13),
weixiao(14),
nanguo(15),
ku(16),
zhuakuang(18),
tu(19),
touxiao(20),
keai(21),
baiyan(22),
aoman(23),
ji_e(24),
kun(25),
jingkong(26),
liuhan(27),
hanxiao(28),
dabing(29),
fendou(30),
zhouma(31),
yiwen(32),
yun(34),
zhemo(35),
shuai(36),
kulou(37),
qiaoda(38),
zaijian(39),
fadou(41),
aiqing(42),
tiaotiao(43),
zhutou(46),
yongbao(49),
dan_gao(53),
shandian(54),
zhadan(55),
dao(56),
zuqiu(57),
bianbian(59),
kafei(60),
fan(61),
meigui(63),
diaoxie(64),
aixin(66),
xinsui(67),
liwu(69),
taiyang(74),
yueliang(75),
qiang(76),
ruo(77),
woshou(78),
shengli(79),
feiwen(85),
naohuo(86),
xigua(89),
lenghan(96),
cahan(97),
koubi(98),
guzhang(99),
qiudale(100),
huaixiao(101),
zuohengheng(102),
youhengheng(103),
haqian(104),
bishi(105),
weiqu(106),
kuaikule(107),
yinxian(108),
qinqin(109),
xia(110),
kelian(111),
caidao(112),
pijiu(113),
lanqiu(114),
pingpang(115),
shiai(116),
piaochong(117),
baoquan(118),
gouyin(119),
quantou(120),
chajin(121),
aini(122),
bu(123),
hao(124),
zhuanquan(125),
ketou(126),
huitou(127),
tiaosheng(128),
huishou(129),
jidong(130),
jiewu(131),
xianwen(132),
zuotaiji(133),
youtaiji(134),
shuangxi(136),
bianpao(137),
denglong(138),
facai(139),
K_ge(140),
gouwu(141),
youjian(142),
shuai_qi(143),
hecai(144),
qidao(145),
baojin(146),
bangbangtang(147),
he_nai(148),
xiamian(149),
xiangjiao(150),
feiji(151),
kaiche(152),
gaotiezuochetou(153),
chexiang(154),
gaotieyouchetou(155),
duoyun(156),
xiayu(157),
chaopiao(158),
xiongmao(159),
dengpao(160),
fengche(161),
naozhong(162),
dasan(163),
caiqiu(164),
zuanjie(165),
shafa(166),
zhijin(167),
yao(168),
shouqiang(169),
qingwa(170);
override fun toString(): String {
return "$name($id)"
}
companion object {
fun ofId(id: Int): FaceID {
for (value in values()) {
if (value.id == id) {
return value
}
}
return unknown
}
}
}
......@@ -5,13 +5,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.image.ClientTryGetImageIDPacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.network.protocol.tim.packet.md5
import net.mamoe.mirai.network.protocol.tim.packet.ClientTryGetImageIDPacketJvm
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.qqNumber
import net.mamoe.mirai.utils.ImageNetworkUtils
import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.toUHexString
import java.awt.image.BufferedImage
......@@ -32,7 +32,7 @@ class UnsolvedImage(private val filename: String, val image: BufferedImage) {
suspend fun upload(session: LoginSession, contact: Contact): CompletableDeferred<Unit> {
return session.expectPacket<ServerTryGetImageIDResponsePacket> {
toSend { ClientTryGetImageIDPacket(session.bot.qqNumber, session.sessionKey, contact.number, image) }
toSend { ClientTryGetImageIDPacketJvm(session.bot.qqNumber, session.sessionKey, contact.number, image) }
onExpect {
when (it) {
......
package net.mamoe.mirai.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
object NetworkScope : CoroutineScope by CoroutineScope(Dispatchers.Default)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketNameFormatter.adjustName
import net.mamoe.mirai.utils.*
import java.io.DataOutputStream
import java.io.IOException
import java.net.InetAddress
import java.security.MessageDigest
/**
* @author Him188moe
*/
abstract class ClientPacket : ByteArrayDataOutputStream(), Packet {
val idHex: String
private var encoded: Boolean = false
init {
val annotation = this.javaClass.getAnnotation(PacketId::class.java)
idHex = annotation.value.trim()
try {
this.writeHex(TIMProtocol.head)
this.writeHex(TIMProtocol.ver)
this.writePacketId()
} catch (e: IOException) {
throw RuntimeException(e)
}
}
@Throws(IOException::class)
fun writePacketId() {
this.writeHex(this@ClientPacket.idHex)
}
/**
* Encode this packet.
*
*
* Before sending the packet, a [tail][TIMProtocol.tail] is added.
*/
@Throws(IOException::class)
protected abstract fun encode()
fun encodePacket() {
if (encoded) {
return
}
encode()
writeHex(TIMProtocol.tail)
}
@Throws(IOException::class)
fun encodeToByteArray(): ByteArray {
encodePacket()
return toByteArray()
}
open fun getFixedId(): String = when (this.idHex.length) {
0 -> "__ __ __ __"
2 -> this.idHex + " __ __ __"
5 -> this.idHex + " __ __"
7 -> this.idHex + " __"
else -> this.idHex
}
override fun toString(): String {
return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "idByteArray" || it.name == "encoded" }.joinToString(", ", "{", "}") {
it.trySetAccessible(); it.name + "=" + it.get(this).let { value ->
when (value) {
null -> null
is ByteArray -> value.toUHexString()
is UByteArray -> value.toUHexString()
else -> value.toString()
}
}
}
}
}
@Throws(IOException::class)
fun DataOutputStream.writeIP(ip: String) {
for (s in ip.trim().split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
this.writeByte(s.toInt())
}
}
@Throws(IOException::class)
fun DataOutputStream.writeTime() {
this.writeInt(System.currentTimeMillis().toInt())
}
@Throws(IOException::class)
fun DataOutputStream.writeHex(uHex: String) {
for (s in uHex.trim().split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
if (s.isEmpty()) {
continue
}
this.writeByte(s.toUByte(16).toInt())
}
}
fun DataOutputStream.encryptAndWrite(byteArray: ByteArray, key: ByteArray) {
this.write(TEA.encrypt(byteArray, key))
}
fun DataOutputStream.encryptAndWrite(key: ByteArray, encoder: ByteArrayDataOutputStream.() -> Unit) {
this.write(TEA.encrypt(ByteArrayDataOutputStream().apply(encoder).use { it.toByteArray() }, key))
}
fun DataOutputStream.encryptAndWrite(keyHex: String, encoder: ByteArrayDataOutputStream.() -> Unit) {
this.encryptAndWrite(keyHex.hexToBytes(), encoder)
}
@Throws(IOException::class)
fun DataOutputStream.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) {
val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + "00 00 00 00".hexToBytes() + qq.toUInt().toByteArray())
this.encryptAndWrite(secondMD5) {
writeRandom(4)
writeHex("00 02")
writeQQ(qq)
writeHex(TIMProtocol.constantData2)
writeHex("00 00 01")
write(firstMD5)
writeInt(loginTime)
writeByte(0)
writeZero(4 * 3)
writeIP(loginIP)
writeZero(8)
writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
write(privateKey)
}
}
@Tested
fun DataOutputStream.writeCRC32() = writeCRC32(getRandomByteArray(16))
fun DataOutputStream.writeCRC32(key: ByteArray) {
write(key)//key
writeInt(getCrc32(key))
}
@Tested
fun DataOutputStream.writeDeviceName(random: Boolean = false) {
val deviceName: String = if (random) {
String(getRandomByteArray(10))
} else {
InetAddress.getLocalHost().hostName
}
this.writeShort(deviceName.length + 2)
this.writeShort(deviceName.length)
this.writeBytes(deviceName)
}
/**
* 255 -> 00 00 00 FF
*/
fun Int.toByteArray(): ByteArray = byteArrayOf(
(this.ushr(24) and 0xFF).toByte(),
(this.ushr(16) and 0xFF).toByte(),
(this.ushr(8) and 0xFF).toByte(),
(this.ushr(0) and 0xFF).toByte()
)
/**
* 255u -> 00 00 00 FF
*/
fun UInt.toByteArray(): ByteArray = byteArrayOf(
(this.shr(24) and 255u).toByte(),
(this.shr(16) and 255u).toByte(),
(this.shr(8) and 255u).toByte(),
(this.shr(0) and 255u).toByte()
)
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
fun md5(str: String): ByteArray = MessageDigest.getInstance("MD5").digest(str.toByteArray())
fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
fun DataOutputStream.writeZero(count: Int) {
repeat(count) {
this.writeByte(0)
}
}
fun DataOutputStream.writeRandom(length: Int) {
repeat(length) {
this.writeByte((Math.random() * 255).toInt())
}
}
fun DataOutputStream.writeQQ(qq: Long) {
this.write(qq.toUInt().toByteArray())
}
fun DataOutputStream.writeGroup(groupIdOrGroupNumber: Long) {
this.write(groupIdOrGroupNumber.toUInt().toByteArray())
}
fun DataOutputStream.writeUByte(uByte: UByte) {
this.write(uByte.toInt())
}
fun DataOutputStream.writeLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size)
this.write(byteArray)
}
fun DataOutputStream.writeLVString(str: String) {
this.writeLVByteArray(str.toByteArray())
}
fun DataOutputStream.writeLVHex(hex: String) {
this.writeLVByteArray(hex.hexToBytes())
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
/**
* @author Him188moe
*/
interface Packet
internal object PacketNameFormatter {
@JvmStatic
private var longestNameLength: Int = 43
@JvmStatic
fun adjustName(name: String): String {
if (name.length > longestNameLength) {
longestNameLength = name.length
return name
}
return " ".repeat(longestNameLength - name.length) + name
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.internal.DangerousInternalIoApi
import net.mamoe.mirai.utils.toUHexString
import java.lang.reflect.Field
internal object PacketNameFormatter {
@JvmStatic
private var longestNameLength: Int = 43
@JvmStatic
fun adjustName(name: String): String {
if (name.length > longestNameLength) {
longestNameLength = name.length
return name
}
return " ".repeat(longestNameLength - name.length) + name
}
}
private object IgnoreIdList : List<String> by listOf(
"idHex",
"fixedId",
"idByteArray",
"encoded",
"packet",
"Companion",
"EMPTY_ID_HEX",
"input",
"output",
"UninitializedByteReadPacket"
)
@UseExperimental(DangerousInternalIoApi::class)
internal actual fun Packet.packetToString(): String = PacketNameFormatter.adjustName(this::class.simpleName + "(${this.fixedId})") + this::class.java.allDeclaredFields
.filterNot { it.name in IgnoreIdList || "delegate" in it.name || "$" in it.name }
.joinToString(", ", "{", "}") {
it.isAccessible = true
it.name + "=" + it.get(this).let { value ->
when (value) {
null -> null
is ByteArray -> value.toUHexString()
is UByteArray -> value.toUHexString()
is ByteReadPacket -> "[ByteReadPacket(${value.remaining})]"
//is ByteReadPacket -> value.copy().readBytes().toUHexString()
is IoBuffer -> "[IoBuffer(${value.readRemaining})]"
else -> value.toString()
}
}
}
private val Class<*>.allDeclaredFields: List<Field>
get() {
val list = mutableListOf<Field>()
var clazz: Class<*> = this
do {
list.addAll(clazz.declaredFields)
} while (clazz.let { clazz = it.superclass; clazz.kotlin != Any::class })
return list
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.network.protocol.tim.packet.PacketNameFormatter.adjustName
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
import net.mamoe.mirai.utils.*
import java.io.DataInputStream
import java.io.EOFException
/**
* @author Him188moe
*/
abstract class ServerPacket(val input: DataInputStream) : Packet {
var idHex: String
var idByteArray: ByteArray//fixed 4 size
var encoded: Boolean = false
init {
idHex = try {
val annotation = this.javaClass.getAnnotation(PacketId::class.java)
annotation.value.trim()
} catch (e: NullPointerException) {
""
}
idByteArray = if (idHex.isEmpty()) {
byteArrayOf(0, 0, 0, 0)
} else {
idHex.hexToBytes()
}
}
fun <P : ServerPacket> P.setId(idHex: String): P {
this.idHex = idHex
return this
}
open fun decode() {
}
companion object {
fun ofByteArray(bytes: ByteArray): ServerPacket {
val stream = bytes.dataInputStream()
stream.skip(3)
val idHex = stream.readInt().toUHexString(" ")
return when (idHex) {
"08 25 31 01" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, stream)
"08 25 31 02" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, stream)
"08 36 31 03", "08 36 31 04", "08 36 31 05", "08 36 31 06" -> {
when (bytes.size) {
271, 207 -> return ServerLoginResponseKeyExchangePacket.Encrypted(stream, when (idHex) {
"08 36 31 03" -> ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`
else -> ServerLoginResponseKeyExchangePacket.Flag.OTHER
}).apply { this.idHex = idHex }
871 -> return ServerLoginResponseVerificationCodeInitPacket.Encrypted(stream).apply { this.idHex = idHex }
}
if (bytes.size > 700) {
return ServerLoginResponseSuccessPacket.Encrypted(stream).apply { this.idHex = idHex }
}
println(bytes.size)
return ServerLoginResponseFailedPacket(when (bytes.size) {
135 -> LoginState.UNKNOWN//账号已经在另一台电脑登录??
319, 351 -> LoginState.WRONG_PASSWORD
//135 -> LoginState.RETYPE_PASSWORD
63, 279 -> LoginState.BLOCKED
263 -> LoginState.UNKNOWN_QQ_NUMBER
551, 487 -> LoginState.DEVICE_LOCK
359 -> LoginState.TAKEN_BACK
else -> LoginState.UNKNOWN
/*
//unknown
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown error)")
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown error)")
else -> throw IllegalArgumentException(bytes.size.toString())*/
}, stream).apply { this.idHex = idHex }
}
"08 28 04 34" -> ServerSessionKeyResponsePacket.Encrypted(stream)
else -> when (idHex.substring(0, 5)) {
"00 EC" -> ServerLoginSuccessPacket(stream)
"00 1D" -> ServerSKeyResponsePacket.Encrypted(stream)
"00 5C" -> ServerAccountInfoResponsePacket.Encrypted(stream)
"00 58" -> ServerHeartbeatResponsePacket(stream)
"00 BA" -> ServerCaptchaPacket.Encrypted(stream, idHex)
"00 CE", "00 17" -> ServerEventPacket.Raw.Encrypted(stream, idHex.hexToBytes())
"00 81" -> UnknownServerPacket(stream)
"00 CD" -> ServerSendFriendMessageResponsePacket(stream)
"00 02" -> ServerSendGroupMessageResponsePacket(stream)
"00 A7" -> ServerCanAddFriendResponsePacket(stream)
"03 88" -> ServerTryGetImageIDResponsePacket.Encrypted(stream)
else -> UnknownServerPacket(stream)
}
}.apply { this.idHex = idHex }
}
}
override fun toString(): String {
return adjustName(this.javaClass.simpleName + "(${this.getFixedId()})") + this.getAllDeclaredFields().filterNot { it.name == "idHex" || it.name == "idByteArray" || it.name == "encoded" }.joinToString(", ", "{", "}") {
it.trySetAccessible(); it.name + "=" + it.get(this).let { value ->
when (value) {
is ByteArray -> value.toUHexString()
is UByteArray -> value.toUHexString()
else -> value?.toString()
}
}
}
}
open fun getFixedId(): String = getFixedId(this.idHex)
fun getFixedId(id: String): String = when (id.length) {
0 -> "__ __ __ __"
2 -> "$id __ __ __"
5 -> "$id __ __"
7 -> "$id __"
else -> id
}
fun decryptBy(key: ByteArray): DataInputStream {
return decryptAsByteArray(key).dataInputStream()
}
fun decryptBy(keyHex: String): DataInputStream {
return this.decryptBy(keyHex.hexToBytes())
}
fun decryptBy(key1: ByteArray, key2: ByteArray): DataInputStream {
return TEA.decrypt(this.decryptAsByteArray(key1), key2).dataInputStream()
}
fun decryptBy(key1: String, key2: ByteArray): DataInputStream {
return this.decryptBy(key1.hexToBytes(), key2)
}
fun decryptBy(key1: ByteArray, key2: String): DataInputStream {
return this.decryptBy(key1, key2.hexToBytes())
}
fun decryptBy(keyHex1: String, keyHex2: String): DataInputStream {
return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
}
fun decryptAsByteArray(key: ByteArray): ByteArray {
input.goto(14)
return TEA.decrypt(input.readAllBytes().cutTail(1), key)
}
fun decryptAsByteArray(keyHex: String): ByteArray = this.decryptAsByteArray(keyHex.hexToBytes())
}
fun DataInputStream.readIP(): String {
var buff = ""
for (i in 0..3) {
val byte = readUnsignedByte()
buff += byte.toString()
if (i != 3) buff += "."
}
return buff
}
fun DataInputStream.readLVString(): String {
return String(this.readLVByteArray())
}
fun DataInputStream.readLVByteArray(): ByteArray {
return this.readNBytes(this.readShort().toInt())
}
fun DataInputStream.readTLVMap(expectingEOF: Boolean = false): Map<Int, ByteArray> {
val map = mutableMapOf<Int, ByteArray>()
var type: Int
try {
type = readUnsignedByte()
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
while (type != 0xff) {
map[type] = this.readLVByteArray()
try {
type = readUnsignedByte()
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
}
return map
}
fun Map<Int, ByteArray>.printTLVMap() {
println(this.mapValues { (_, value) -> value.toUHexString() })
}
fun DataInputStream.readString(length: Number): String {
return String(this.readNBytes(length))
}
fun ByteArray.dataInputStream(): DataInputStream = DataInputStream(this.inputStream())
/**
* Reset and skip(position)
*/
fun <N : Number> DataInputStream.goto(position: N): DataInputStream {
this.reset()
this.skip(position.toLong())
return this
}
fun <N : Number> DataInputStream.readNBytesAt(position: N, length: Int): ByteArray {
this.goto(position)
return this.readNBytes(length)
}
fun <N : Number> DataInputStream.readNBytes(length: N): ByteArray {
return this.readNBytes(length.toInt())
}
fun DataInputStream.readLVNumber(): Number {
return when (this.readShort().toInt()) {
1 -> this.readByte()
2 -> this.readShort()
4 -> this.readInt()
8 -> this.readLong()
else -> throw UnsupportedOperationException()
}
}
fun DataInputStream.readNBytesIn(range: IntRange): ByteArray {
this.goto(range.first)
return this.readNBytes(range.last - range.first + 1)
}
fun <N : Number> DataInputStream.readIntAt(position: N): Int {
this.goto(position)
return this.readInt()
}
fun <N : Number> DataInputStream.readUIntAt(position: N): UInt {
this.goto(position)
return this.readNBytes(4).toUInt()
}
fun DataInputStream.readUInt(): UInt {
return this.readNBytes(4).toUInt()
}
fun <N : Number> DataInputStream.readByteAt(position: N): Byte {
this.goto(position)
return this.readByte()
}
fun <N : Number> DataInputStream.readShortAt(position: N): Short {
this.goto(position)
return this.readShort()
}
//添加@JvmSynthetic 导致 idea 无法检查这个文件的错误
//@JvmSynthetic
fun DataInputStream.gotoWhere(matcher: UByteArray): DataInputStream {
return this.gotoWhere(matcher.toByteArray())
}
/**
* 去往下一个含这些连续字节的位置
*/
@Throws(EOFException::class)
fun DataInputStream.gotoWhere(matcher: ByteArray): DataInputStream {
require(matcher.isNotEmpty())
loop@
do {
val byte = this.readByte()
if (byte == matcher[0]) {
//todo mark here
for (i in 1 until matcher.size) {
val b = this.readByte()
if (b != matcher[i]) {
continue@loop //todo goto mark
}
}
return this
}
} while (true)
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
/**
* @author Him188moe
*/
class UnknownServerPacket(input: DataInputStream) : ServerPacket(input) {
override fun decode() {
MiraiLogger.debug("UnknownServerPacket data: " + this.input.goto(0).readAllBytes().toUHexString())
}
override fun toString(): String {
return LoggerTextFormat.LIGHT_RED.toString() + super.toString()
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet.image
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.writeUVarInt
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeFully
import net.mamoe.mirai.utils.*
import java.awt.image.BufferedImage
import java.io.DataInputStream
actual typealias PlatformImage = BufferedImage
actual typealias ClientTryGetImageIDPacket = ClientTryGetImageIDPacketJvm
/**
* 请求上传图片. 将发送图片的 md5, size.
......@@ -17,13 +20,13 @@ import java.io.DataInputStream
* @author Him188moe
*/
@PacketId("03 88")
class ClientTryGetImageIDPacket(
class ClientTryGetImageIDPacketJvm(
private val botNumber: Long,
private val sessionKey: ByteArray,
private val groupNumberOrQQNumber: Long,
private val image: BufferedImage
private val image: PlatformImage
) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(botNumber)
......@@ -55,7 +58,7 @@ class ClientTryGetImageIDPacket(
writeHex("22")
writeHex("10")
write(md5(byteArray))
writeFully(md5(byteArray))
writeHex("28")
writeUVarInt(byteArray.size.toUInt())//E2 0D
......@@ -92,41 +95,3 @@ class ClientTryGetImageIDPacket(
}
}
}
\ No newline at end of file
abstract class ServerTryGetImageIDResponsePacket(input: DataInputStream) : ServerPacket(input) {
class Encrypted(input: DataInputStream) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey)
println(data.size)
println(data.size)
if (data.size == 209) {
return ServerTryGetImageIDSuccessPacket(data.dataInputStream()).setId(this.idHex)
}
return ServerTryGetImageIDFailedPacket(data.dataInputStream())
}
}
}
/**
* 服务器未存有图片, 返回一个 key 用于客户端上传
*/
class ServerTryGetImageIDSuccessPacket(input: DataInputStream) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray
override fun decode() {
this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))
uKey = this.input.readNBytes(128)
}
}
/**
* 服务器已经存有这个图片
*/
class ServerTryGetImageIDFailedPacket(input: DataInputStream) : ServerTryGetImageIDResponsePacket(input) {
override fun decode() {
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.DataInputStream
/**
* @author Him188moe
*/
class ServerLoginResponseFailedPacket(val loginState: LoginState, input: DataInputStream) : ServerPacket(input) {
override fun decode() {
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.goto
import net.mamoe.mirai.utils.Tested
import java.io.DataInputStream
/**
* 服务器进行加密后返回 privateKey
*
* @author NaturalHG
*/
@PacketId("08 36 31 03")
class ServerLoginResponseKeyExchangePacket(input: DataInputStream, val flag: Flag) : ServerPacket(input) {
enum class Flag {
`08 36 31 03`,
OTHER,
}
lateinit var tlv0006: ByteArray//120bytes
var tokenUnknown: ByteArray? = null
lateinit var privateKeyUpdate: ByteArray//16bytes
@Tested
override fun decode() {
this.input.skip(5)
privateKeyUpdate = this.input.readNBytes(16)//22
//this.input.skip(2)//25
this.input.goto(25)
tlv0006 = this.input.readNBytes(120)
when (flag) {
Flag.`08 36 31 03` -> {
tokenUnknown = this.input.goto(153).readNBytes(56)
//println(tokenUnknown!!.toUHexString())
}
Flag.OTHER -> {
//do nothing in this packet.
//[this.token] will be set in [BotNetworkHandler]
//token
}
}
}
class Encrypted(input: DataInputStream, private val flag: Flag) : ServerPacket(input) {
@Tested
fun decrypt(privateKey: ByteArray): ServerLoginResponseKeyExchangePacket {
return ServerLoginResponseKeyExchangePacket(this.decryptBy(TIMProtocol.shareKey, privateKey), flag).setId(this.idHex)
}
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.goto
import net.mamoe.mirai.network.protocol.tim.packet.readNBytesAt
import net.mamoe.mirai.network.protocol.tim.packet.readString
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
/**
* @author NaturalHG
*/
class ServerLoginResponseSuccessPacket(input: DataInputStream) : ServerPacket(input) {
lateinit var sessionResponseDecryptionKey: ByteArray//16 bytes|
lateinit var nickname: String
lateinit var token38: ByteArray
lateinit var token88: ByteArray
lateinit var encryptionKey: ByteArray
@Tested
override fun decode() {
this.input.skip(7)//8
this.encryptionKey = this.input.readNBytes(16)//24
this.input.skip(2)//26
this.token38 = this.input.readNBytes(56)//82
this.input.skip(60L)//142
val msgLength = when (val id = this.input.readNBytes(2).toUByteArray().toUHexString()) {
"01 07" -> 0
"00 33" -> 28
"01 10" -> 64
else -> throw IllegalStateException(id)
}
this.sessionResponseDecryptionKey = this.input.readNBytesAt(171 + msgLength, 16)
this.token88 = this.input.readNBytesAt(189 + msgLength, 136)
val nickLength = this.input.goto(624 + msgLength).readByte().toInt()
this.nickname = this.input.readString(nickLength)
//this.age = this.input.goto(packetDataLength - 28).readShortAt()
//this.gender = this.input.goto(packetDataLength - 32).readByteAt().toInt()
}
class Encrypted(input: DataInputStream) : ServerPacket(input) {
fun decrypt(privateKey: ByteArray): ServerLoginResponseSuccessPacket {
input.goto(14)
return ServerLoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).setId(this.idHex)
}
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.dataInputStream
import net.mamoe.mirai.network.protocol.tim.packet.goto
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.hexToUBytes
import java.io.DataInputStream
/**
* 收到这个包意味着需要验证码登录, 并且能得到验证码图片文件的一部分
*
* @author Him188moe
*/
class ServerLoginResponseVerificationCodeInitPacket(input: DataInputStream, private val packetLength: Int) : ServerPacket(input) {
lateinit var verifyCodePart1: ByteArray
lateinit var token00BA: ByteArray
var unknownBoolean: Boolean? = null
@Tested
override fun decode() {
val verifyCodeLength = this.input.goto(78).readShort()//2bytes
this.verifyCodePart1 = this.input.readNBytes(verifyCodeLength.toInt())
this.input.skip(1)
this.unknownBoolean = this.input.readByte().toInt() == 1
this.token00BA = this.input.goto(packetLength - 60).readNBytes(40)
}
class Encrypted(input: DataInputStream) : ServerPacket(input) {
override fun decode() {
}
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket = this.decryptAsByteArray(TIMProtocol.shareKey).let {
ServerLoginResponseVerificationCodeInitPacket(it.dataInputStream(), it.size).setId(this.idHex)
}
}
}
fun main() {
val data = "FB 01 04 03 33 00 01 00 BA 02 03 2C 13 00 05 01 00 00 01 23 00 38 D5 01 05 8B 67 4D 52 5A FA 92 DB 99 18 D4 F0 72 03 E0 17 71 7C 8A 45 74 1F C3 2D F8 61 96 0D 93 0D 8C 51 95 70 F8 F9 CB B9 2D 5D BC 4F 5D 89 5F E7 59 8C E4 E5 A2 04 56 02 BC 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 45 50 4C 54 45 FE F6 ED E2 F1 DF F3 FF F2 11 77 48 FE FE F3 F1 F9 EA D7 FD E7 F8 F9 EC FC EF E7 E8 FF EE 2D 69 48 2A 8A 5D 29 7A 52 F0 ED E1 A9 C7 B1 65 96 79 AB E0 C2 C3 F0 D5 42 7D 5C 4A 99 72 89 AA 93 51 73 5C 6E BA 94 42 BD 7A 0B 00 00 09 C5 49 44 41 54 58 C3 AC 99 8B 76 AB 3A 12 44 91 D0 1B 10 08 04 FF FF A9 B3 5B 60 C7 AF 38 77 EE 0C EB C4 76 6C 07 95 BA AB AB AB 75 BA EE 1F 5D DE CB 63 08 C1 C7 A8 AD BC B6 31 46 6B 3B A3 79 94 E7 F3 63 79 E4 D9 98 89 37 7D F7 FF BB 3C 10 82 6F B7 F7 2A B6 5B 07 EF B5 16 60 DE 1B 63 3A 1F 7E BE 6D 0C 50 EC 09 E1 FE 76 B8 3F FC 6B 0C 1D 5B 37 1F 3E E1 AE 61 9A D8 B6 07 93 B7 56 4D 66 22 06 9D FC EE 5F BF F9 3F 81 90 BB 99 F3 49 E2 1D DA 43 38 13 44 14 8C 9D B4 8E E7 65 40 11 E4 BB FE 8C 5B 78 41 F1 AF 01 48 2E 08 6D 8B AD F1 BA 05 F9 C4 E0 25 E6 31 BA EB 2A 90 C3 74 26 FC 1E 80 CF 14 91 44 7F FC 39 FF D4 37 10 82 C0 B3 B0 67 BF 2E 5E 5B F4 46 E5 3A 5F D7 BE A6 54 17 2D 7C 0D D7 DA E1 7E 93 C7 E5 DB 9E 6E 9F FD A3 14 9C 7F 23 97 55 FA DC 6F 74 8A CC 04 49 03 3F DE 4E 5C 4A 95 9C 53 8D 7A EA 82 F9 10 77 7F EE E5 43 28 C2 9F 00 BA 5B C0 59 A4 CE DB BA AE DB BC 14 D5 2A 82 A5 83 E0 30 92 19 A8 99 36 A7 15 7C BC DF F6 BE D7 86 C0 7F C9 C5 ED 93 C7 1F B9 E0 56 CB AC 61 F5 5D 2E 00 F0 93 D2 5A 9D B6 B0 3F 4B 4D 5C B9 0F C1 EE 29 46 65 4E 40 46 E2 E4 1B 91 C3 A5 2E FE D3 7A 3C 84 0B E3 F3 E2 57 16 2F 08 B9 E6 5A 73 29 65 29 4E B9 25 CF 6B 9A 97 48 20 B2 35 46 1B 6D AD D6 9A 57 69 45 B2 1A 00 90 05 51 8D C7 24 F8 1B B9 FE A6 41 B8 83 6C 59 05 84 9F 44 0F 85 05 9A 97 5A 97 BC A6 6D 81 FE 59 DE 2F 4B 5E E4 DF B2 A4 19 AA 06 D9 FE F9 33 4D 7E 6A 40 FC 97 34 BF 84 E4 81 81 ED E9 DC 85 32 56 47 E5 A4 F0 2D 6F 4D 2A BA 65 4B 73 89 B6 58 5E D7 35 8D 69 E4 4A 6B 76 50 C1 5C 3A D9 59 11 CF 37 99 FA 48 88 70 7F F4 9F 22 12 F2 24 91 3E 2B BF 28 A5 34 68 C0 50 A3 55 DD A4 E3 9C 6E 85 99 95 B6 24 2E 18 D9 3C 5C B1 4D AA 2F 08 E1 75 F1 F0 6B 49 FC BC E3 8D 00 01 00 28 42 E6 18 57 D4 B1 4D AE 51 27 D5 EF A2 38 91 39 15 37 6C 5A FE 75 93 49 DB FC 57 3C 12 3F 26 D9 16 1D 83 45 8B 78 39 D8 01 15 00 10 F6 F0 50 03 74 BB 18 91 D3 55 8D 7F BB 53 15 7A".hexToUBytes().toByteArray()
ServerLoginResponseVerificationCodeInitPacket(
data.dataInputStream(),
data.size
).let { it.decode(); println(it) }
}
/*
data
FB 01 04 03 33 00 01 00 BA 02 03 2C 13 00 05 01 00 00 01 23 00 38 D5 01 05 8B 67 4D 52 5A FA 92 DB 99 18 D4 F0 72 03 E0 17 71 7C 8A 45 74 1F C3 2D F8 61 96 0D 93 0D 8C 51 95 70 F8 F9 CB B9 2D 5D BC 4F 5D 89 5F E7 59 8C E4 E5 A2 04 56 02 BC 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 45 50 4C 54 45 FE F6 ED E2 F1 DF F3 FF F2 11 77 48 FE FE F3 F1 F9 EA D7 FD E7 F8 F9 EC FC EF E7 E8 FF EE 2D 69 48 2A 8A 5D 29 7A 52 F0 ED E1 A9 C7 B1 65 96 79 AB E0 C2 C3 F0 D5 42 7D 5C 4A 99 72 89 AA 93 51 73 5C 6E BA 94 42 BD 7A 0B 00 00 09 C5 49 44 41 54 58 C3 AC 99 8B 76 AB 3A 12 44 91 D0 1B 10 08 04 FF FF A9 B3 5B 60 C7 AF 38 77 EE 0C EB C4 76 6C 07 95 BA AB AB AB 75 BA EE 1F 5D DE CB 63 08 C1 C7 A8 AD BC B6 31 46 6B 3B A3 79 94 E7 F3 63 79 E4 D9 98 89 37 7D F7 FF BB 3C 10 82 6F B7 F7 2A B6 5B 07 EF B5 16 60 DE 1B 63 3A 1F 7E BE 6D 0C 50 EC 09 E1 FE 76 B8 3F FC 6B 0C 1D 5B 37 1F 3E E1 AE 61 9A D8 B6 07 93 B7 56 4D 66 22 06 9D FC EE 5F BF F9 3F 81 90 BB 99 F3 49 E2 1D DA 43 38 13 44 14 8C 9D B4 8E E7 65 40 11 E4 BB FE 8C 5B 78 41 F1 AF 01 48 2E 08 6D 8B AD F1 BA 05 F9 C4 E0 25 E6 31 BA EB 2A 90 C3 74 26 FC 1E 80 CF 14 91 44 7F FC 39 FF D4 37 10 82 C0 B3 B0 67 BF 2E 5E 5B F4 46 E5 3A 5F D7 BE A6 54 17 2D 7C 0D D7 DA E1 7E 93 C7 E5 DB 9E 6E 9F FD A3 14 9C 7F 23 97 55 FA DC 6F 74 8A CC 04 49 03 3F DE 4E 5C 4A 95 9C 53 8D 7A EA 82 F9 10 77 7F EE E5 43 28 C2 9F 00 BA 5B C0 59 A4 CE DB BA AE DB BC 14 D5 2A 82 A5 83 E0 30 92 19 A8 99 36 A7 15 7C BC DF F6 BE D7 86 C0 7F C9 C5 ED 93 C7 1F B9 E0 56 CB AC 61 F5 5D 2E 00 F0 93 D2 5A 9D B6 B0 3F 4B 4D 5C B9 0F C1 EE 29 46 65 4E 40 46 E2 E4 1B 91 C3 A5 2E FE D3 7A 3C 84 0B E3 F3 E2 57 16 2F 08 B9 E6 5A 73 29 65 29 4E B9 25 CF 6B 9A 97 48 20 B2 35 46 1B 6D AD D6 9A 57 69 45 B2 1A 00 90 05 51 8D C7 24 F8 1B B9 FE A6 41 B8 83 6C 59 05 84 9F 44 0F 85 05 9A 97 5A 97 BC A6 6D 81 FE 59 DE 2F 4B 5E E4 DF B2 A4 19 AA 06 D9 FE F9 33 4D 7E 6A 40 FC 97 34 BF 84 E4 81 81 ED E9 DC 85 32 56 47 E5 A4 F0 2D 6F 4D 2A BA 65 4B 73 89 B6 58 5E D7 35 8D 69 E4 4A 6B 76 50 C1 5C 3A D9 59 11 CF 37 99 FA 48 88 70 7F F4 9F 22 12 F2 24 91 3E 2B BF 28 A5 34 68 C0 50 A3 55 DD A4 E3 9C 6E 85 99 95 B6 24 2E 18 D9 3C 5C B1 4D AA 2F 08 E1 75 F1 F0 6B 49 FC BC E3 8D 00 01 00 28 42 E6 18 57 D4 B1 4D AE 51 27 D5 EF A2 38 91 39 15 37 6C 5A FE 75 93 49 DB FC 57 3C 12 3F 26 D9 16 1D 83 45 8B 78 39 D8 01 15 00 10 F6 F0 50 03 74 BB 18 91 D3 55 8D 7F BB 53 15 7A
length 700
verify code
89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 00 82 00 00 00 35 08 03 00 00 00 BA 12 C3 02 00 00 00 04 67 41 4D 41 00 00 B1 8F 0B FC 61 05 00 00 00 01 73 52 47 42 00 AE CE 1C E9 00 00 00 45 50 4C 54 45 FE F6 ED E2 F1 DF F3 FF F2 11 77 48 FE FE F3 F1 F9 EA D7 FD E7 F8 F9 EC FC EF E7 E8 FF EE 2D 69 48 2A 8A 5D 29 7A 52 F0 ED E1 A9 C7 B1 65 96 79 AB E0 C2 C3 F0 D5 42 7D 5C 4A 99 72 89 AA 93 51 73 5C 6E BA 94 42 BD 7A 0B 00 00 09 C5 49 44 41 54 58 C3 AC 99 8B 76 AB 3A 12 44 91 D0 1B 10 08 04 FF FF A9 B3 5B 60 C7 AF 38 77 EE 0C EB C4 76 6C 07 95 BA AB AB AB 75 BA EE 1F 5D DE CB 63 08 C1 C7 A8 AD BC B6 31 46 6B 3B A3 79 94 E7 F3 63 79 E4 D9 98 89 37 7D F7 FF BB 3C 10 82 6F B7 F7 2A B6 5B 07 EF B5 16 60 DE 1B 63 3A 1F 7E BE 6D 0C 50 EC 09 E1 FE 76 B8 3F FC 6B 0C 1D 5B 37 1F 3E E1 AE 61 9A D8 B6 07 93 B7 56 4D 66 22 06 9D FC EE 5F BF F9 3F 81 90 BB 99 F3 49 E2 1D DA 43 38 13 44 14 8C 9D B4 8E E7 65 40 11 E4 BB FE 8C 5B 78 41 F1 AF 01 48 2E 08 6D 8B AD F1 BA 05 F9 C4 E0 25 E6 31 BA EB 2A 90 C3 74 26 FC 1E 80 CF 14 91 44 7F FC 39 FF D4 37 10 82 C0 B3 B0 67 BF 2E 5E 5B F4 46 E5 3A 5F D7 BE A6 54 17 2D 7C 0D D7 DA E1 7E 93 C7 E5 DB 9E 6E 9F FD A3 14 9C 7F 23 97 55 FA DC 6F 74 8A CC 04 49 03 3F DE 4E 5C 4A 95 9C 53 8D 7A EA 82 F9 10 77 7F EE E5 43 28 C2 9F 00 BA 5B C0 59 A4 CE DB BA AE DB BC 14 D5 2A 82 A5 83 E0 30 92 19 A8 99 36 A7 15 7C BC DF F6 BE D7 86 C0 7F C9 C5 ED 93 C7 1F B9 E0 56 CB AC 61 F5 5D 2E 00 F0 93 D2 5A 9D B6 B0 3F 4B 4D 5C B9 0F C1 EE 29 46 65 4E 40 46 E2 E4 1B 91 C3 A5 2E FE D3 7A 3C 84 0B E3 F3 E2 57 16 2F 08 B9 E6 5A 73 29 65 29 4E B9 25 CF 6B 9A 97 48 20 B2 35 46 1B 6D AD D6 9A 57 69 45 B2 1A 00 90 05 51 8D C7 24 F8 1B B9 FE A6 41 B8 83 6C 59 05 84 9F 44 0F 85 05 9A 97 5A 97 BC A6 6D 81 FE 59 DE 2F 4B 5E E4 DF B2 A4 19 AA 06 D9 FE F9 33 4D 7E 6A 40 FC 97 34 BF 84 E4 81 81 ED E9 DC 85 32 56 47 E5 A4 F0 2D 6F 4D 2A BA 65 4B 73 89 B6 58 5E D7 35 8D 69 E4 4A 6B 76 50 C1 5C 3A D9 59 11 CF 37 99 FA 48 88 70 7F F4 9F 22 12 F2 24 91 3E 2B BF 28 A5 34 68 C0 50 A3 55 DD A4 E3 9C 6E 85 99 95 B6 24 2E 18 D9 3C 5C B1 4D AA 2F 08 E1 75 F1 F0 6B 49 FC BC E3 8D
token00ba
42 E6 18 57 D4 B1 4D AE 51 27 D5 EF A2 38 91 39 15 37 6C 5A FE 75 93 49 DB FC 57 3C 12 3F 26 D9 16 1D 83 45 8B 78 39 D8
*/
package net.mamoe.mirai.utils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import java.awt.Image
import java.awt.image.BufferedImage
import java.io.File
import javax.imageio.ImageIO
import kotlin.math.max
import kotlin.math.min
/**
* 让用户处理验证码
*
* @return 用户输入得到的验证码
*/
internal actual suspend fun solveCaptcha(captchaBuffer: IoBuffer): String? = captchaLock.withLock {
val captcha = captchaBuffer.readBytes()
withContext(Dispatchers.IO) {
MiraiLogger.logCyan(ImageIO.read(captcha.inputStream()).createCharImg())
}
MiraiLogger.logCyan("需要验证码登录, 验证码为 4 字母")
try {
File(System.getProperty("user.dir") + "/temp/Captcha.png")
.also { withContext(Dispatchers.IO) { it.createNewFile(); it.writeBytes(captcha) } }
MiraiLogger.logCyan("若看不清字符图片, 请查看 Mirai 目录下 /temp/Captcha.png")
} catch (e: Exception) {
MiraiLogger.logCyan("无法写出验证码文件, 请尝试查看以上字符图片")
}
MiraiLogger.logCyan("若要更换验证码, 请直接回车")
readLine()?.takeUnless { it.isEmpty() || it.length != 4 }
}
private val captchaLock = Mutex()
/**
* @author NaturalHG
*/
@JvmOverloads
internal fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String {
/*
* resize Image
* */
val newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt()
val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH)
val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB)
val g2d = image.createGraphics()
g2d.drawImage(tmp, 0, 0, null)
fun gray(rgb: Int): Int {
val r = rgb and 0xff0000 shr 16
val g = rgb and 0x00ff00 shr 8
val b = rgb and 0x0000ff
return (r * 30 + g * 59 + b * 11 + 50) / 100
}
fun grayCompare(g1: Int, g2: Int): Boolean = min(g1, g2).toDouble() / max(g1, g2) >= ignoreRate
val background = gray(image.getRGB(0, 0))
return buildString(capacity = height) {
val lines = mutableListOf<StringBuilder>()
var minXPos = outputWidth
var maxXPos = 0
for (y in 0 until image.height) {
val builderLine = StringBuilder()
for (x in 0 until image.width) {
val gray = gray(image.getRGB(x, y))
if (grayCompare(gray, background)) {
builderLine.append(" ")
} else {
builderLine.append("#")
if (x < minXPos) {
minXPos = x
}
if (x > maxXPos) {
maxXPos = x
}
}
}
if (builderLine.toString().isBlank()) {
continue
}
lines.add(builderLine)
}
for (line in lines) {
append(line.substring(minXPos, maxXPos)).append("\n")
}
}
}
package net.mamoe.mirai.utils
import java.awt.Image
import java.awt.image.BufferedImage
import java.util.*
import java.util.concurrent.Callable
import kotlin.math.max
import kotlin.math.min
/**
* Convert IMAGE into Chars that could shows in terminal
*
* @author NaturalHG
*/
class CharImageConverter @JvmOverloads constructor(
/**
* width should depends on the width of the terminal
*/
private var image: BufferedImage?, private val width: Int, private val ignoreRate: Double = 0.95) : Callable<String> {
override fun call(): String {
/*
* resize Image
* */
val newHeight = (this.image!!.height * (width.toDouble() / this.image!!.width)).toInt()
val tmp = image!!.getScaledInstance(width, newHeight, Image.SCALE_SMOOTH)
val dimg = BufferedImage(width, newHeight, BufferedImage.TYPE_INT_ARGB)
val g2d = dimg.createGraphics()
g2d.drawImage(tmp, 0, 0, null)
this.image = dimg
val background = gray(image!!.getRGB(0, 0))
val builder = StringBuilder()
val lines = ArrayList<StringBuilder>(this.image!!.height)
var minXPos = this.width
var maxXPos = 0
for (y in 0 until image!!.height) {
val builderLine = StringBuilder()
for (x in 0 until image!!.width) {
val gray = gray(image!!.getRGB(x, y))
if (grayCompare(gray, background)) {
builderLine.append(" ")
} else {
builderLine.append("#")
if (x < minXPos) {
minXPos = x
}
if (x > maxXPos) {
maxXPos = x
}
}
}
if (builderLine.toString().isBlank()) {
continue
}
lines.add(builderLine)
}
for (line in lines) {
builder.append(line.substring(minXPos, maxXPos)).append("\n")
}
return builder.toString()
}
private fun gray(rgb: Int): Int {
val R = rgb and 0xff0000 shr 16
val G = rgb and 0x00ff00 shr 8
val B = rgb and 0x0000ff
return (R * 30 + G * 59 + B * 11 + 50) / 100
}
fun grayCompare(g1: Int, g2: Int): Boolean {
return min(g1, g2).toDouble() / max(g1, g2) >= ignoreRate
}
}
package net.mamoe.mirai.utils
import java.awt.image.BufferedImage
/**
* @author NaturalHG
*/
@JvmOverloads
fun BufferedImage.createCharImg(sizeWeight: Int = 100): String {
return CharImageConverter(this, sizeWeight).call()
}
\ No newline at end of file
package net.mamoe.mirai.utils;
/**
* @author NaturalHG
*/
public class EventException extends RuntimeException {
private final Throwable cause;
public EventException(Throwable throwable) {
cause = throwable;
}
public EventException() {
cause = null;
}
public EventException(Throwable cause, String message) {
super(message);
this.cause = cause;
}
public EventException(String message) {
super(message);
cause = null;
}
@Override
public Throwable getCause() {
return cause;
}
}
package net.mamoe.mirai.utils
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL
import javax.imageio.ImageIO
/**
* @author NaturalHG
......@@ -29,3 +32,6 @@ object ImageNetworkUtils {
return conn.responseCode == 200
}
}
fun BufferedImage.toByteArray(): ByteArray = ByteArrayOutputStream().use { ImageIO.write(this, "JPG", it); it.toByteArray() }
\ No newline at end of file
package net.mamoe.mirai.utils
import java.text.SimpleDateFormat
import java.util.*
/**
* @author Him188moe
*/
interface MiraiLogger {
companion object : MiraiLogger by defaultLogger()
var identity: String
fun info(any: Any?) = log(any)
fun log(any: Any?)
fun error(any: Any?)
fun debug(any: Any?)
fun cyan(any: Any?)
fun purple(any: Any?)
fun green(any: Any?)
fun blue(any: Any?)
}
/**
* 由 mirai-console 或 mirai-web 等模块实现
*/
var defaultLogger: () -> MiraiLogger = { Console() }
val DEBUGGING: Boolean by lazy {
//avoid inspections
true
}
open class Console(
override var identity: String = "[Unknown]"
) : MiraiLogger {
override fun green(any: Any?) = print(any.toString(), LoggerTextFormat.GREEN)
override fun purple(any: Any?) = print(any.toString(), LoggerTextFormat.LIGHT_PURPLE)
override fun blue(any: Any?) = print(any.toString(), LoggerTextFormat.BLUE)
override fun cyan(any: Any?) = print(any.toString(), LoggerTextFormat.LIGHT_CYAN)
override fun error(any: Any?) = print(any.toString(), LoggerTextFormat.RED)
override fun log(any: Any?) = print(any.toString(), LoggerTextFormat.LIGHT_GRAY)
override fun debug(any: Any?) {
if (DEBUGGING) {
print(any.toString(), LoggerTextFormat.YELLOW)
}
}
fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.YELLOW) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
println("$color$identity $s : $value")
}
}
\ No newline at end of file
package net.mamoe.mirai.utils
import java.text.SimpleDateFormat
import java.util.*
actual typealias PlatformLogger = Console
open class Console @JvmOverloads constructor(
override var identity: String? = null
) : MiraiLogger {
override fun logGreen(any: Any?) = println(any.toString(), LoggerTextFormat.GREEN)
override fun logPurple(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_PURPLE)
override fun logBlue(any: Any?) = println(any.toString(), LoggerTextFormat.BLUE)
override fun logCyan(any: Any?) = println(any.toString(), LoggerTextFormat.LIGHT_CYAN)
override fun logError(any: Any?) = println(any.toString(), LoggerTextFormat.RED)
override fun log(e: Throwable) = e.printStackTrace()
override fun log(any: Any?) = println(any.toString())//kotlin println
override fun logDebug(any: Any?) {
if (DEBUGGING) {
println(any.toString(), LoggerTextFormat.YELLOW)
}
}
private fun println(value: String?, color: LoggerTextFormat) {
val time = SimpleDateFormat("HH:mm:ss").format(Date())
if (identity == null) {
println("$color$time : $value")
} else {
println("$color$identity $time : $value")
}
}
}
private val DEBUGGING: Boolean by lazy {
//avoid inspections
true
}
\ No newline at end of file
package net.mamoe.mirai.utils
import java.net.InetAddress
import java.security.MessageDigest
import java.util.zip.CRC32
actual val currentTime: Long = System.currentTimeMillis()
actual val deviceName: String = InetAddress.getLocalHost().hostName
actual fun crc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray)
actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(hostname).hostAddress
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
\ No newline at end of file
package net.mamoe.mirai.utils
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
actual class MiraiDatagramChannel actual constructor(serverHost: String, serverPort: Short) : Closeable {
private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt())
private val channel: DatagramChannel = DatagramChannel.open().connect(serverAddress)
actual suspend fun read(buffer: IoBuffer) = withContext(Dispatchers.IO) { (channel as ReadableByteChannel).read(buffer) }
actual suspend fun send(buffer: IoBuffer) = withContext(Dispatchers.IO) { buffer.readDirect { channel.send(it, serverAddress) } }
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
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import java.nio.ByteBuffer
import java.util.*
import kotlin.experimental.and
......@@ -10,10 +12,10 @@ import kotlin.experimental.xor
*
* @author iweiz https://github.com/iweizime/StepChanger/blob/master/app/src/main/java/me/iweizi/stepchanger/qq/Cryptor.java
*/
object TEA {
actual object TEA {
private const val UINT32_MASK = 0xffffffffL
private fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray {
internal actual fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray {
val mRandom = Random()
lateinit var mOutput: ByteArray
lateinit var mInBlock: ByteArray
......@@ -162,7 +164,7 @@ object TEA {
}
fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray? {
require(!(len % 8 != 0 || len < 16)) { "data must len % 8 == 0 && len >= 16" }
require(!(len % 8 != 0 || len < 16)) { "data must len % 8 == 0 && len >= 16 but given $len" }
mIV = decode(cipherText, offset)
mIndexPos = (mIV[0] and 7).toInt()
var plen = len - mIndexPos - 10
......@@ -240,20 +242,23 @@ object TEA {
}
}
fun encrypt(source: ByteArray, key: ByteArray): ByteArray {
@JvmStatic
actual fun encrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, true)
}
@Suppress("unused")
fun encrypt(source: ByteArray, keyHex: String): ByteArray {
return encrypt(source, keyHex.hexToBytes())
@JvmStatic
actual fun decrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, false)
}
fun decrypt(source: ByteArray, key: ByteArray): ByteArray {
return doOption(source, key, false)
@JvmStatic
actual fun decrypt(source: ByteArray, key: IoBuffer): ByteArray {
return doOption(source, key.readBytes(), false)
}
fun decrypt(source: ByteArray, keyHex: String): ByteArray {
@JvmStatic
actual fun decrypt(source: ByteArray, keyHex: String): ByteArray {
return decrypt(source, keyHex.hexToBytes())
}
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.dataInputStream
import java.awt.image.BufferedImage
import java.io.ByteArrayOutputStream
import java.io.DataInputStream
import java.io.DataOutputStream
import java.io.File
import java.lang.reflect.Field
import java.util.*
import java.util.zip.CRC32
import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream
import javax.imageio.ImageIO
import kotlin.random.Random
import kotlin.random.nextInt
/**
* @author Him188moe
* @author NaturalHG
*/
//@JvmSynthetic
fun ByteArray.toHexString(): String = toHexString(" ")
fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
}
return@joinToString ret
}
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator)
//@JvmSynthetic
fun ByteArray.toUHexString(): String = this.toUByteArray().toUHexString()
//@JvmSynthetic
fun UByteArray.toUHexString(separator: String = " "): String {
return this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
}
return@joinToString ret
}
}
//@JvmSynthetic
fun UByteArray.toUHexString(): String = this.toUHexString(" ")
fun Byte.toUHexString(): String = this.toUByte().toString(16)
fun String.hexToBytes(): ByteArray = TIMProtocol.hexToBytes(this)
fun String.hexToUBytes(): UByteArray = TIMProtocol.hexToUBytes(this)
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
fun ByteArray.toUInt(): UInt =
this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0)
open class ByteArrayDataOutputStream : DataOutputStream(ByteArrayOutputStream()) {
open fun toByteArray(): ByteArray = (out as ByteArrayOutputStream).toByteArray()
open fun toUByteArray(): UByteArray = (out as ByteArrayOutputStream).toByteArray().toUByteArray()
}
fun dataEncode(t: (ByteArrayDataOutputStream) -> Unit): ByteArray = ByteArrayDataOutputStream().also(t).toByteArray()
fun <R> dataDecode(byteArray: ByteArray, t: (DataInputStream) -> R): R = byteArray.dataInputStream().let(t)
fun <R> ByteArray.decode(t: (DataInputStream) -> R): R = this.dataInputStream().let(t)
fun ByteArray.decryptBy(key: ByteArray): ByteArray = TEA.decrypt(this, key)
fun ByteArray.decryptBy(key: String): ByteArray = TEA.decrypt(this, key)
fun DataInputStream.skip(n: Number) {
this.skip(n.toLong())
}
fun getRandomByteArray(length: Int): ByteArray = List(length) { Random.Default.nextInt(0..255).toByte() }.toByteArray()
operator fun File.plus(child: String): File = File(this, child)
private const val GTK_BASE_VALUE: Int = 5381
internal fun getGTK(sKey: String): Int {
var value = GTK_BASE_VALUE
for (c in sKey.toCharArray()) {
value += (value shl 5) + c.toInt()
}
value = value and Int.MAX_VALUE
return value
}
internal fun getCrc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
/**
* 获取类的所有字段(类成员变量), 包括父类的和私有的. <br></br>
* 相当于将这个类和它所有父类的 [Class.getDeclaredFields] 都合并成一个 [List] <br></br>
* 不会排除重名的字段. <br></br>
*
* @return field list
*/
@Throws(SecurityException::class)
fun Any.getAllDeclaredFields(): List<Field> {
var clazz: Class<*> = this.javaClass
val list = LinkedList<Field>()
loop@ do {
if (!clazz.name.contains("net.mamoe")) {
break@loop
}
list.addAll(clazz.declaredFields.filter { (it.name == "Companion" || it.name == "input").not() }.toList())
if (clazz.superclass == null) {
break
}
clazz = clazz.superclass
} while (clazz != Object::javaClass)
return list
}
private const val ZERO_BYTE: Byte = 0
fun ByteArray.removeZeroTail(): ByteArray {
var i = this.size - 1
while (this[i] == ZERO_BYTE) {
--i
}
return this.copyOfRange(0, i + 1)
}
fun BufferedImage.toByteArray(formatName: String = "PNG"): ByteArray {
return dataEncode {
ImageIO.write(this, formatName, it)
}
}
object GZip {
fun uncompress(bytes: ByteArray): ByteArray = dataEncode {
GZIPInputStream(bytes.inputStream()).transferTo(it)
}
fun compress(bytes: ByteArray): ByteArray = ByteArrayOutputStream().let {
GZIPOutputStream(it).write(bytes)
return it.toByteArray()
}
}
fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
fun ByteArray.getRight(length: Int): ByteArray = this.copyOfRange(this.size - length, this.size)
\ No newline at end of file
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.login
import net.mamoe.mirai.network.NetworkScope
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
import java.util.*
......@@ -36,7 +35,7 @@ const val qqList = "" +
suspend fun main() {
val goodBotList = Collections.synchronizedList(mutableListOf<Bot>())
withContext(NetworkScope.coroutineContext) {
withContext(GlobalScope.coroutineContext) {
qqList.split("\n")
.filterNot { it.isEmpty() }
.map { it.split("----") }
......@@ -54,7 +53,7 @@ suspend fun main() {
withContext(Dispatchers.IO) {
bot.login()
}.let { state ->
if (state == LoginState.SUCCESS) {
if (state == LoginResult.SUCCESS) {
goodBotList.add(bot)
}
}
......
......@@ -7,6 +7,7 @@ dependencies {
compile rootProject.ext.coroutineCommon
compile rootProject.ext.kotlinJvm
compile rootProject.ext.kotlinxIOJvm
}
tasks.withType(JavaCompile) {
......
@file:Suppress("ObjectPropertyName", "unused", "NonAsciiCharacters", "MayBeConstant")
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.toUHexString
import net.mamoe.mirai.utils.toUHexString
import java.awt.Toolkit
import java.awt.datatransfer.DataFlavor
......@@ -246,6 +245,41 @@ object HexComparator {
}
fun colorize(hex1s: String): String {
val builder = StringBuilder()
val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
val constMatcher1 = ConstMatcher(hex1s)
val numberLine = StringBuilder()
val hex1ConstName = StringBuilder()
val hex1b = StringBuilder()
buildConstNameChain(hex1s.length, constMatcher1, hex1ConstName)
for (i in hex1.indices) {
var h1: String? = null
val matchedConstName = constMatcher1.getMatchedConstName(i)
if (matchedConstName != null) {
h1 = BLUE + hex1[i]
}
if (h1 == null) {
h1 = hex1[i]
h1 = GREEN + h1
}
numberLine.append(UNKNOWN).append(getFixedNumber(i)).append(" ")
hex1b.append(" ").append(h1).append(" ")
}
return builder.append("\n")
.append(numberLine).append("\n")
.append(hex1ConstName).append("\n")
.append(hex1b).append("\n")
.toString()
}
private fun getFixedNumber(number: Int): String {
if (number < 10) {
......
......@@ -3,6 +3,8 @@
import jpcap.JpcapCaptor
import jpcap.packet.IPPacket
import jpcap.packet.UDPPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.message.internal.readMessageChain
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
......@@ -93,7 +95,7 @@ object Main {
//println("--------------")
//println("接收数据包")
//println("raw packet = " + data.toUHexString())
packetReceived(ServerPacket.ofByteArray(data))
packetReceived(data.read { this.parseServerPacket(data.size) })
}
fun packetReceived(packet: ServerPacket) {
......@@ -106,8 +108,8 @@ object Main {
is UnknownServerEventPacket -> {
println("--------------")
println("未知事件ID=" + packet.packetId.toUHexString())
println("未知事件: " + packet.input.readAllBytes().toUHexString())
println("未知事件ID=" + packet.idHex)
println("未知事件: " + packet.input.readBytes().toUHexString())
}
is ServerEventPacket -> {
......@@ -124,41 +126,41 @@ object Main {
}
}
fun dataSent(rawPacket: ByteArray) = rawPacket.cutTail(1).decode { packet ->
fun dataSent(rawPacket: ByteArray) = rawPacket.cutTail(1).read {
println("---------------------------")
packet.skip(3)//head
val idHex = packet.readNBytes(4).toUHexString()
discardExact(3)//head
val idHex = readBytes(4).toUHexString()
println("发出包ID = $idHex")
packet.skip(TIMProtocol.fixVer2.hexToBytes().size + 1 + 5 - 3 + 1)
discardExact(TIMProtocol.fixVer2.hexToBytes().size + 1 + 5 - 3 + 1)
val encryptedBody = packet.readAllBytes()
val encryptedBody = readRemainingBytes()
println("body = ${encryptedBody.toUHexString()}")
encryptedBody.decode { data ->
encryptedBody.read {
when (idHex.substring(0, 5)) {
"00 CD" -> {
println("好友消息")
val raw = data.readAllBytes()
val raw = readRemainingBytes()
println("解密前数据: " + raw.toUHexString())
val messageData = raw.decryptBy(sessionKey)
println("解密结果: " + messageData.toUHexString())
println("尝试解消息")
messageData.decode {
it.skip(
messageData.read {
discardExact(
4 + 4 + 12 + 2 + 4 + 4 + 16 + 2 + 2 + 4 + 2 + 16 + 4 + 4 + 7 + 15 + 2
+ 1
)
val chain = it.readMessageChain()
val chain = readMessageChain()
println(chain)
}
}
"03 88" -> {
println("上传图片-获取图片ID")
data.skip(8)
val body = data.readAllBytes().decryptBy(sessionKey)
discardExact(8)
val body = readRemainingBytes().decryptBy(sessionKey)
println(body.toUHexString())
}
}
......
package demo1
import kotlinx.coroutines.delay
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.event.events.FriendMessageEvent
import net.mamoe.mirai.event.events.GroupMessageEvent
import net.mamoe.mirai.event.subscribeAll
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeOnce
import net.mamoe.mirai.event.subscribeUntilFalse
import net.mamoe.mirai.login
import net.mamoe.mirai.message.Image
import net.mamoe.mirai.message.PlainText
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
import net.mamoe.mirai.utils.MiraiLogger
import kotlin.system.exitProcess
/**
* @author Him188moe
*/
suspend fun main() {
val bot = Bot(BotAccount(//填写你的账号
qqNumber = 1994701121,
password = "xiaoqqq"
qqNumber = 1994701021,
password = "asdhim188666"
), Console())
bot.login().let {
if (it != LoginState.SUCCESS) {
MiraiLogger.error("Login failed: " + it.name)
bot.login {
touchTimeoutMillis = 2000
randomDeviceName = true
}.let {
if (it != LoginResult.SUCCESS) {
MiraiLogger.logError("Login failed: " + it.name)
exitProcess(0)
}
}
//提供泛型以监听事件
subscribeAlways<FriendMessageEvent> {
//获取第一个纯文本消息
subscribeOnce<FriendMessageEvent> {
//获取第一个纯文本消息, 获取不到会抛出 NoSuchElementException
val firstText = it.message.first<PlainText>()
//获取第一个图片
val firstImage = it.message.first<Image>()
val firstImage = it.message.firstOrNull<Image>()
when {
it.message eq "你好" -> it.reply("你好!")
......@@ -53,13 +55,13 @@ suspend fun main() {
}
}
/*it.message eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
/*it.event eq "发图片群" -> sendGroupMessage(Group(session.bot, 580266363), PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, Group(session.bot, 580266363)).of()
})*/
it.message eq "发图片群2" -> Group(bot, 580266363).sendMessage(Image("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))
/* it.message eq "发图片" -> sendFriendMessage(it.sender, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
/* it.event eq "发图片" -> sendFriendMessage(it.sender, PlainText("test") + UnsolvedImage(File("C:\\Users\\Him18\\Desktop\\faceImage_1559564477775.jpg")).also { image ->
image.upload(session, it.sender).of()
})*/
it.message eq "发图片2" -> it.reply(PlainText("test") + Image("{7AA4B3AA-8C3C-0F45-2D9B-7F302A0ACEAA}.jpg"))
......@@ -82,6 +84,9 @@ suspend fun main() {
}
}
//由于使用的是协程, main函数执行完后就会结束程序.
delay(Long.MAX_VALUE)//永远等待, 以测试事件
}
......
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