Commit d89f8acc authored by Him188's avatar Him188

Multiplatform

parent c09d44f4
...@@ -4,6 +4,7 @@ ext { ...@@ -4,6 +4,7 @@ ext {
// kotlin // kotlin
kotlinJvm = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" kotlinJvm = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
kotlinCommon = "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version" kotlinCommon = "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
kotlinNative = "org.jetbrains.kotlin:kotlin-stdlib-native:$kotlin_version"
// coroutine // coroutine
coroutine_version = "1.3.0" coroutine_version = "1.3.0"
...@@ -13,6 +14,8 @@ ext { ...@@ -13,6 +14,8 @@ ext {
coroutineAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" coroutineAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
coroutineJs = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutine_version" coroutineJs = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutine_version"
coroutineIo = "org.jetbrains.kotlinx:kotlinx-coroutines-io:0.24.0"
// reflect // reflect
reflect = "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" reflect = "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
...@@ -25,5 +28,6 @@ ext { ...@@ -25,5 +28,6 @@ ext {
kotlinxIOJvm = "org.jetbrains.kotlinx:kotlinx-io-jvm:$kotlinx_io_version" kotlinxIOJvm = "org.jetbrains.kotlinx:kotlinx-io-jvm:$kotlinx_io_version"
kotlinxIOCommon = "org.jetbrains.kotlinx:kotlinx-io:$kotlinx_io_version" kotlinxIOCommon = "org.jetbrains.kotlinx:kotlinx-io:$kotlinx_io_version"
kotlinxIOJS = "org.jetbrains.kotlinx:kotlinx-io-js:$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 package net.mamoe.mirai
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
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.* 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.config.MiraiConfig
import net.mamoe.mirai.utils.setting.MiraiSettings import net.mamoe.mirai.utils.setting.MiraiSettings
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
/**
* @author Him188moe
*/
/** /**
* Mirai 服务器. * Mirai 服务器.
...@@ -40,11 +39,11 @@ object MiraiServer { ...@@ -40,11 +39,11 @@ object MiraiServer {
this.logger = MiraiLogger this.logger = MiraiLogger
logger.info("About to run Mirai (" + Mirai.VERSION + ") under " + if (isUnix) "unix" else "windows") logger.logInfo("About to run Mirai (" + Mirai.VERSION + ") under " + if (isUnix) "unix" else "windows")
logger.info("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder) logger.logInfo("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder)
val setting = this.parentFolder + "/Mirai.ini" val setting = File(this.parentFolder, "/Mirai.ini")
logger.info("Selecting setting from " + LoggerTextFormat.GREEN + setting) logger.logInfo("Selecting setting from " + LoggerTextFormat.GREEN + setting)
/* /*
if (!setting.exists()) { if (!setting.exists()) {
...@@ -54,7 +53,7 @@ object MiraiServer { ...@@ -54,7 +53,7 @@ object MiraiServer {
} }
File qqs = new File(this.parentFolder + "/QQ.yml"); 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()) { if (!qqs.exists()) {
this.initQQConfig(qqs); this.initQQConfig(qqs);
} else { } else {
...@@ -67,10 +66,10 @@ object MiraiServer { ...@@ -67,10 +66,10 @@ object MiraiServer {
/* /*
MiraiSettingMapSection qqs = this.setting.getMapSection("qq"); MiraiSettingMapSection qqs = this.setting.getMapSection("qq");
qqs.forEach((a,p) -> { qqs.forEach((a,p) -> {
this.getLogger().info("Finding available ports between " + "1-65536"); this.getLogger().logInfo("Finding available ports between " + "1-65536");
try { try {
int port = MiraiNetwork.getAvailablePort(); int port = MiraiNetwork.getAvailablePort();
this.getLogger().info("Listening on port " + port); this.getLogger().logInfo("Listening on port " + port);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
...@@ -84,18 +83,18 @@ object MiraiServer { ...@@ -84,18 +83,18 @@ object MiraiServer {
fun shutdown() { fun shutdown() {
if (this.enabled) { if (this.enabled) {
logger.info("About to shutdown Mirai") logger.logInfo("About to shutdown Mirai")
logger.info("Data have been saved") logger.logInfo("Data have been saved")
} }
} }
private fun initSetting(setting: File) { private fun initSetting(setting: File) {
logger.info("Thanks for using Mirai") logger.logInfo("Thanks for using Mirai")
logger.info("initializing Settings") logger.logInfo("initializing Settings")
try { try {
if (setting.createNewFile()) { if (setting.createNewFile()) {
logger.info("Mirai Config Created") logger.logInfo("Mirai Config Created")
} }
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
...@@ -113,24 +112,24 @@ object MiraiServer { ...@@ -113,24 +112,24 @@ object MiraiServer {
worker["core_task_pool_worker_amount"] = 5 worker["core_task_pool_worker_amount"] = 5
val plugin = this.settings.getMapSection("plugin") val plugin = this.settings.getMapSection("plugin")
plugin["debug"] = false plugin["logDebug"] = false
this.settings.save() 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) { private fun initQQConfig(qqConfig: File) {
this.qqs = MiraiConfig(qqConfig) this.qqs = MiraiConfig(qqConfig)
MiraiLogger.info("QQ account initialized; changing can be made in Config file: $qqConfig") MiraiLogger.logInfo("QQ account initialized; changing can be made in Config file: $qqConfig")
logger.info("QQ 账户管理初始化完毕") logger.logInfo("QQ 账户管理初始化完毕")
} }
private fun reload() { private fun reload() {
this.enabled = true this.enabled = true
MiraiLogger.info(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai") MiraiLogger.logInfo(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai")
MiraiLogger.info("Mirai Version=" + Mirai.VERSION) MiraiLogger.logInfo("Mirai Version=" + Mirai.VERSION)
MiraiLogger.info("Initializing [Bot]s") MiraiLogger.logInfo("Initializing [Bot]s")
try { try {
availableBot availableBot
...@@ -142,23 +141,23 @@ object MiraiServer { ...@@ -142,23 +141,23 @@ object MiraiServer {
/* /*
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> { 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 { try {
Bot bot = new Bot(section); Bot bot = new Bot(section);
var state = bot.network.login$mirai_core().of(); var state = bot.network.login$mirai_core().of();
//bot.network.login$mirai_core().whenComplete((state, e) -> { //bot.network.login$mirai_core().whenComplete((state, e) -> {
if (state == LoginState.SUCCESS) { if (state == LoginState.SUCCESS) {
Bot.instances.add(bot); Bot.instances.add(bot);
getLogger().green(" Login Succeed"); getLogger().logGreen(" Login Succeed");
} else { } else {
getLogger().error(" Login Failed with error " + state); getLogger().logError(" Login Failed with logError " + state);
bot.close(); bot.close();
} }
// }).of(); // }).of();
} catch (Throwable e) { } catch (Throwable e) {
e.printStackTrace(); e.printStackTrace();
getLogger().error("Could not load QQ bots config!"); getLogger().logError("Could not load QQ bots config!");
System.exit(1); System.exit(1);
} }
});*/ });*/
...@@ -173,9 +172,9 @@ object MiraiServer { ...@@ -173,9 +172,9 @@ object MiraiServer {
get() { get() {
for (it in qqList.split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()) { for (it in qqList.split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()) {
val strings = it.split("----").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") bot.green("Login succeed")
return bot return bot
} }
......
...@@ -4,22 +4,40 @@ apply plugin: "kotlin-multiplatform" ...@@ -4,22 +4,40 @@ apply plugin: "kotlin-multiplatform"
kotlin { kotlin {
targets { targets {
fromPreset(presets.jvm, "jvm") fromPreset(presets.jvm, "jvm")
//fromPreset(presets.mingwX64, "mingwX64")
} }
jvm() 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 { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation rootProject.ext.kotlinCommon implementation rootProject.ext.kotlinCommon
implementation rootProject.ext.reflect 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.atomicFUCommon
implementation rootProject.ext.kotlinxIOCommon implementation rootProject.ext.kotlinxIOCommon
} }
} }
jvmMain { jvmMain {
apply plugin: 'java' apply plugin: 'java'
...@@ -35,6 +53,23 @@ kotlin { ...@@ -35,6 +53,23 @@ kotlin {
implementation 'org.ini4j:ini4j:0.5.2' 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 { jvmTest {
} }
...@@ -46,17 +81,4 @@ kotlin { ...@@ -46,17 +81,4 @@ kotlin {
compileKotlinJvm { 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 package net.mamoe.mirai
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot.ContactSystem import net.mamoe.mirai.Bot.ContactSystem
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
...@@ -12,12 +14,12 @@ import net.mamoe.mirai.utils.MiraiLogger ...@@ -12,12 +14,12 @@ import net.mamoe.mirai.utils.MiraiLogger
/** /**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号. * Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人. * Mirai 为多账号设计, 可同时维护多个机器人.
* <br></br> *
* [Bot] 由 3 个模块组成. * [Bot] 由 3 个模块组成.
* [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问 * [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问
* [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问 * [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问
* [机器人账号信息][BotAccount]: 可通过 [Bot.account] 访问 * [机器人账号信息][BotAccount]: 可通过 [Bot.account] 访问
* <br></br> *
* 若你需要得到机器人的 QQ 账号, 请访问 [Bot.account] * 若你需要得到机器人的 QQ 账号, 请访问 [Bot.account]
* 若你需要得到服务器上所有机器人列表, 请访问 [Bot.instances] * 若你需要得到服务器上所有机器人列表, 请访问 [Bot.instances]
* *
...@@ -28,7 +30,7 @@ import net.mamoe.mirai.utils.MiraiLogger ...@@ -28,7 +30,7 @@ import net.mamoe.mirai.utils.MiraiLogger
* a [ContactSystem], which manage contacts such as [QQ] and [Group]; * a [ContactSystem], which manage contacts such as [QQ] and [Group];
* a [TIMBotNetworkHandler], which manages the connection to the server; * a [TIMBotNetworkHandler], which manages the connection to the server;
* a [BotAccount], which stores the account information(e.g. qq number the bot) * 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 QQ contacts, access [Bot.account]
* To of all the Robot instance, access [Bot.instances] * To of all the Robot instance, access [Bot.instances]
* *
...@@ -42,7 +44,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { ...@@ -42,7 +44,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
val contacts = ContactSystem() val contacts = ContactSystem()
val network: BotNetworkHandler = TIMBotNetworkHandler(this) val network: BotNetworkHandler<*> = TIMBotNetworkHandler(this)
init { init {
instances.add(this) instances.add(this)
...@@ -59,25 +61,22 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { ...@@ -59,25 +61,22 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
*/ */
inner class ContactSystem internal constructor() { inner class ContactSystem internal constructor() {
val groups = ContactList<Group>() val groups = ContactList<Group>()
private val groupsLock = Mutex()
val qqs = ContactList<QQ>() 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) */
} fun getQQ(qqNumber: Long): QQ = qqs.getOrPut(qqNumber) { QQ(this@Bot, qqNumber) }
return this.qqs[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 { fun getGroupById(groupId: Long): Group {
return getGroupByNumber(Group.groupIdToNumber(groupId)) return getGroupByNumber(Group.groupIdToNumber(groupId))
...@@ -86,7 +85,6 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { ...@@ -86,7 +85,6 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
fun close() { fun close() {
this.network.close() this.network.close()
this.contacts.groups.values.forEach { it.close() }
this.contacts.groups.clear() this.contacts.groups.clear()
this.contacts.qqs.clear() this.contacts.qqs.clear()
} }
...@@ -94,8 +92,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) { ...@@ -94,8 +92,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
companion object { companion object {
val instances: MutableList<Bot> = mutableListOf() val instances: MutableList<Bot> = mutableListOf()
private var id = 0 private val id = atomic(0)
private val idLock = Any() fun nextId(): Int = id.addAndGet(1)
fun nextId(): Int = synchronized(idLock) { id++ }
} }
} }
\ No newline at end of file
@file:Suppress("unused") @file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai package net.mamoe.mirai
import kotlinx.io.core.readBytes
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket 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.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.goto import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.utils.ContactList 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 net.mamoe.mirai.utils.toUHexString
import java.text.SimpleDateFormat
import java.util.*
/** /**
* The mirror of functions in inner classes of [Bot] * The mirror of functions in inner classes of [Bot]
...@@ -23,7 +21,10 @@ import java.util.* ...@@ -23,7 +21,10 @@ import java.util.*
//Contacts //Contacts
fun Bot.getQQ(number: Long): QQ = this.contacts.getQQ(number) 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: 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) fun Bot.getGroupById(number: Long): Group = this.contacts.getGroupById(number)
...@@ -33,9 +34,11 @@ val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs ...@@ -33,9 +34,11 @@ val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
//NetworkHandler //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 //BotAccount
val Bot.qqNumber: Long get() = this.account.qqNumber val Bot.qqNumber: Long get() = this.account.qqNumber
...@@ -45,31 +48,19 @@ 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.log(o: Any?) = info(o)
fun Bot.println(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.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE) fun Bot.error(o: Any?) = this.logger.logError(o)
fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE) fun Bot.purple(o: Any?) = this.logger.logPurple(o)
fun Bot.cyan(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN) fun Bot.cyan(o: Any?) = this.logger.logCyan(o)
fun Bot.green(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN) fun Bot.green(o: Any?) = this.logger.logGreen(o)
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) { fun Bot.printPacketDebugging(packet: ServerPacket) {
debug("Packet=$packet") debug("Packet=$packet")
debug("Packet size=" + packet.input.goto(0).readAllBytes().size) debug("Packet size=" + packet.input.readBytes().size)
debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString()) debug("Packet data=" + packet.input.readBytes().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")
} }
\ No newline at end of file
package net.mamoe.mirai package net.mamoe.mirai
//expect fun s(): String
/**
* @author Him188moe
*/
object Mirai { object Mirai {
const val VERSION: String = "1.0.0" const val VERSION: String = "1.0.0"
} }
\ No newline at end of file
...@@ -4,7 +4,7 @@ import net.mamoe.mirai.Bot ...@@ -4,7 +4,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group.Companion.groupNumberToId import net.mamoe.mirai.contact.Group.Companion.groupNumberToId
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.utils.ContactList import net.mamoe.mirai.utils.ContactList
import java.io.Closeable import kotlin.jvm.JvmStatic
/** /**
* 群. * 群.
...@@ -21,22 +21,20 @@ import java.io.Closeable ...@@ -21,22 +21,20 @@ import java.io.Closeable
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)` * Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @author Him188moe * @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 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) { 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 suspend fun sendXMLMessage(message: String) {
} }
override fun close() {
this.members.clear()
}
companion object { companion object {
@JvmStatic @JvmStatic
fun groupNumberToId(number: Long): Long {//求你别出错 fun groupNumberToId(number: Long): Long {//求你别出错
......
...@@ -12,34 +12,26 @@ import net.mamoe.mirai.message.MessageChain ...@@ -12,34 +12,26 @@ import net.mamoe.mirai.message.MessageChain
* Java 获取 qq 号: `qq.getNumber()` * Java 获取 qq 号: `qq.getNumber()`
* Java 获取所属 bot: `qq.getBot()` * 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. * Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
* *
* @author Him188moe * @author Him188moe
*/ */
class QQ(bot: Bot, number: Long) : Contact(bot, number) { class QQ(bot: Bot, number: Long) : Contact(bot, number) {
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain) {
bot.network.message.sendFriendMessage(this, message) bot.network.event.sendFriendMessage(this, message)
} }
override suspend fun sendXMLMessage(message: String) { override suspend fun sendXMLMessage(message: String) {
} }
/**
* At(@) this account.
*
* @return an instance of [Message].
*/
fun at(): At {
return At(this)
}
/*
Make that we can use (QQ + QQ2 + QQ3).sendMessage( )
operator fun plus(qq: QQ): QQCombination {
}*/
} }
/**
* At(@) this account.
*
* @return an instance of [Message].
*/
fun QQ.at(): At {
return At(this)
}
\ No newline at end of file
...@@ -2,10 +2,16 @@ ...@@ -2,10 +2,16 @@
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.event.internal.broadcastInternal 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 ...@@ -13,7 +19,6 @@ import net.mamoe.mirai.event.internal.broadcastInternal
* *
* @see [broadcast] 广播事件 * @see [broadcast] 广播事件
* @see [subscribe] 监听事件 * @see [subscribe] 监听事件
* @author Him188moe
*/ */
abstract class Event { abstract class Event {
...@@ -31,7 +36,6 @@ abstract class Event { ...@@ -31,7 +36,6 @@ abstract class Event {
* *
* @throws UnsupportedOperationException 如果事件没有实现 [Cancellable] * @throws UnsupportedOperationException 如果事件没有实现 [Cancellable]
*/ */
@Throws(UnsupportedOperationException::class)
fun cancel() { fun cancel() {
cancelled = true cancelled = true
} }
...@@ -39,8 +43,6 @@ abstract class Event { ...@@ -39,8 +43,6 @@ abstract class Event {
/** /**
* 实现这个接口的事件可以被取消. * 实现这个接口的事件可以被取消.
*
* @author Him188moe
*/ */
interface Cancellable { interface Cancellable {
val cancelled: Boolean val cancelled: Boolean
...@@ -51,8 +53,23 @@ interface Cancellable { ...@@ -51,8 +53,23 @@ interface Cancellable {
/** /**
* 广播一个事件的唯一途径 * 广播一个事件的唯一途径
*/ */
@Synchronized
@Suppress("UNCHECKED_CAST") @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 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 ...@@ -5,6 +5,7 @@ package net.mamoe.mirai.event
import net.mamoe.mirai.event.internal.Handler import net.mamoe.mirai.event.internal.Handler
import net.mamoe.mirai.event.internal.listeners import net.mamoe.mirai.event.internal.listeners
import net.mamoe.mirai.event.internal.subscribeInternal import net.mamoe.mirai.event.internal.subscribeInternal
import kotlin.jvm.Synchronized
import kotlin.reflect.KClass import kotlin.reflect.KClass
enum class ListeningStatus { enum class ListeningStatus {
...@@ -76,8 +77,8 @@ inline fun <reified E : Event> subscribeAll(noinline listeners: ListenerBuilder< ...@@ -76,8 +77,8 @@ inline fun <reified E : Event> subscribeAll(noinline listeners: ListenerBuilder<
* } * }
* *
* untilFalse { * untilFalse {
* it.reply("你发送了 ${it.message}") * it.reply("你发送了 ${it.event}")
* it.message eq "停止" * it.event eq "停止"
* } * }
* } * }
* ``` * ```
......
...@@ -3,9 +3,7 @@ package net.mamoe.mirai.event.events ...@@ -3,9 +3,7 @@ package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
/**
* @author Him188moe
*/
abstract class BotEvent(val bot: Bot) : Event() abstract class BotEvent(val bot: Bot) : Event()
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot) class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
\ No newline at end of file
...@@ -5,9 +5,7 @@ import net.mamoe.mirai.contact.QQ ...@@ -5,9 +5,7 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
*/
abstract class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot) abstract class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot)
/** /**
...@@ -16,15 +14,11 @@ 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 * @author Him188moe
*/ */
class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : FriendEvent(bot, sender) { class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : FriendEvent(bot, sender) {
@JvmSynthetic
suspend inline fun reply(message: Message) = sender.sendMessage(message) suspend inline fun reply(message: Message) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: String) = sender.sendMessage(message) suspend inline fun reply(message: String) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: List<Message>) = sender.sendMessage(message) suspend inline fun reply(message: List<Message>) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)//shortcut 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 ...@@ -6,24 +6,16 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
*/
abstract class GroupEvent(bot: Bot, val group: Group) : BotEvent(bot) 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) { 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) suspend inline fun reply(message: Message) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: String) = group.sendMessage(message) suspend inline fun reply(message: String) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: List<Message>) = group.sendMessage(message) suspend inline fun reply(message: List<Message>) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: MessageChain) = group.sendMessage(message) 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 ...@@ -8,9 +8,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
/* Abstract */ /* Abstract */
/**
* @author Him188moe
*/
sealed class PacketEvent<out P : Packet>(bot: Bot, open val packet: P) : BotEvent(bot) sealed class PacketEvent<out P : Packet>(bot: Bot, open val packet: P) : BotEvent(bot)
......
package net.mamoe.mirai.event.internal package net.mamoe.mirai.event.internal
import kotlinx.coroutines.delay import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.ListeningStatus 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.KClass
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.isSuperclassOf
/** /**
* 监听和广播实现 * 监听和广播实现
...@@ -42,29 +33,31 @@ class Handler<E : Event>(val handler: suspend (E) -> ListeningStatus) : Listener ...@@ -42,29 +33,31 @@ class Handler<E : Event>(val handler: suspend (E) -> ListeningStatus) : Listener
internal val <E : Event> KClass<E>.listeners: EventListeners<E> get() = EventListenerManger.get(this) 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 { internal object EventListenerManger {
private val registries: MutableMap<KClass<out Event>, EventListeners<out Event>> = mutableMapOf() private val registries: MutableMap<KClass<out Event>, EventListeners<out Event>> = mutableMapOf()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
internal fun <E : Event> get(clazz: KClass<E>): EventListeners<E> { internal fun <E : Event> get(clazz: KClass<E>): EventListeners<E> {
synchronized(clazz) { //synchronized(clazz) {
if (registries.containsKey(clazz)) { if (registries.containsKey(clazz)) {
return registries[clazz] as EventListeners<E> return registries[clazz] as EventListeners<E>
} else { } else {
EventListeners<E>().let { EventListeners<E>().let {
registries[clazz] = it registries[clazz] = it
return it return it
}
} }
} }
//}
} }
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
internal suspend fun <E : Event> E.broadcastInternal(): E { 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() val iterator = listeners.iterator()
while (iterator.hasNext()) { while (iterator.hasNext()) {
if (iterator.next().onEvent(this) == ListeningStatus.STOPPED) { if (iterator.next().onEvent(this) == ListeningStatus.STOPPED) {
...@@ -74,24 +67,9 @@ internal suspend fun <E : Event> E.broadcastInternal(): E { ...@@ -74,24 +67,9 @@ internal suspend fun <E : Event> E.broadcastInternal(): E {
} }
callListeners(this::class.listeners as EventListeners<in 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 return this
} }
suspend fun main() { internal expect inline fun <E : Event> loopAllListeners(clazz: KClass<E>, consumer: (EventListeners<in E>) -> Unit)
ServerPacketReceivedEvent::class.subscribeAlways { \ No newline at end of file
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
@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 ...@@ -13,12 +13,12 @@ import net.mamoe.mirai.contact.QQ
* 这与使用 [String] 的使用非常类似. * 这与使用 [String] 的使用非常类似.
* *
* 比较 [Message] 与 [String] (使用 infix [Message.eq]): * 比较 [Message] 与 [String] (使用 infix [Message.eq]):
* `if(message eq "你好") qq.sendMessage(message)` * `if(event eq "你好") qq.sendMessage(event)`
* *
* 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]): * 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]):
* ``` * ```
* message = PlainText("Hello ") * event = PlainText("Hello ")
* qq.sendMessage(message + "world") * qq.sendMessage(event + "world")
* ``` * ```
* *
* 但注意: 不能 `String + Message`. 只能 `Message + String` * 但注意: 不能 `String + Message`. 只能 `Message + String`
......
...@@ -2,11 +2,9 @@ ...@@ -2,11 +2,9 @@
package net.mamoe.mirai.message package net.mamoe.mirai.message
/**
* @author Him188moe
*/
@Suppress("unused") @Suppress("unused")
enum class MessageType(private val value: UByte) { enum class MessageType(val value: UByte) {
PLAIN_TEXT(0x03u), PLAIN_TEXT(0x03u),
AT(0x06u), AT(0x06u),
FACE(0x02u), FACE(0x02u),
......
...@@ -9,8 +9,4 @@ fun String.toMessage(): PlainText = PlainText(this) ...@@ -9,8 +9,4 @@ fun String.toMessage(): PlainText = PlainText(this)
/** /**
* 用 `this` 构造 [MessageChain] * 用 `this` 构造 [MessageChain]
*/ */
fun Message.toChain(): MessageChain = if (this is MessageChain) this else MessageChain(this) 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 package net.mamoe.mirai.message.internal
import kotlinx.io.core.*
import net.mamoe.mirai.message.* import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.* 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 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 //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, 也可能下面那个 val id1 = FaceID.ofId(readLVNumber().toInt().toUByte())//可能这个是id, 也可能下面那个
it.skip(it.readByte().toLong()) discardExact(readByte().toLong())
it.readLVNumber()//某id? readLVNumber()//某id?
return@dataDecode Face(id1) return@read Face(id1)
} }
internal fun ByteArray.parsePlainText(): PlainText = dataDecode(this) { internal fun ByteArray.parsePlainText(): PlainText = read {
it.skip(1) discardExact(1)
PlainText(it.readLVString()) PlainText(readLVString())
} }
internal fun ByteArray.parseMessageImage0x06(): Image = dataDecode(this) { internal fun ByteArray.parseMessageImage0x06(): Image = read {
it.skip(1) discardExact(1)
MiraiLogger.debug("好友的图片") MiraiLogger.logDebug("好友的图片")
MiraiLogger.debug(this.toUHexString()) MiraiLogger.logDebug(this@parseMessageImage0x06.toUHexString())
val filenameLength = it.readShort() val filenameLength = readShort()
val suffix = it.readString(filenameLength).substringAfter(".") val suffix = readString(filenameLength).substringAfter(".")
it.skip(this.size - 37 - 1 - filenameLength - 2) discardExact(this@parseMessageImage0x06.size - 37 - 1 - filenameLength - 2)
val imageId = String(it.readNBytes(36)) val imageId = readString(36)
MiraiLogger.debug(imageId) MiraiLogger.logDebug(imageId)
it.skip(1)//0x41 discardExact(1)//0x41
return@dataDecode Image("{$imageId}.$suffix") return@read Image("{$imageId}.$suffix")
} }
internal fun ByteArray.parseMessageImage0x03(): Image = dataDecode(this) { internal fun ByteArray.parseMessageImage0x03(): Image = read {
it.skip(1) discardExact(1)
return@dataDecode Image(String(it.readLVByteArray())) return@read Image(String(readLVByteArray()))
/* /*
println(String(it.readLVByteArray())) println(String(readLVByteArray()))
it.readTLVMap() readTLVMap()
return@dataDecode Image(String(it.readLVByteArray().cutTail(5).getRight(42))) return Image(String(readLVByteArray().cutTail(5).getRight(42)))
/ /
it.skip(data.size - 47) discardExact(data.size - 47)
val imageId = String(it.readNBytes(42)) val imageId = String(readBytes(42))
it.skip(1)//0x41 discardExact(1)//0x41
it.skip(1)//0x42 discardExact(1)//0x42
it.skip(1)//0x43 discardExact(1)//0x43
it.skip(1)//0x41 discardExact(1)//0x41
return@dataDecode Image(imageId)*/ return Image(imageId)*/
} }
internal fun ByteArray.parseMessageChain(): MessageChain = dataDecode(this) { internal fun ByteArray.parseMessageChain(): MessageChain = read {
it.readMessageChain() readMessageChain()
} }
internal fun DataInputStream.readMessage(): Message? { internal fun ByteReadPacket.readMessage(): Message? {
val messageType = this.readByte().toInt() val messageType = this.readByte().toInt()
val sectionLength = this.readShort().toLong()//sectionLength: short val sectionLength = this.readShort().toLong()//sectionLength: short
val sectionData = this.readNBytes(sectionLength) val sectionData = this.readBytes(sectionLength.toInt())//use buffer instead
return when (messageType) { return when (messageType) {
0x01 -> sectionData.parsePlainText() 0x01 -> sectionData.parsePlainText()
0x02 -> sectionData.parseMessageFace() 0x02 -> sectionData.parseMessageFace()
...@@ -81,7 +82,7 @@ internal fun DataInputStream.readMessage(): Message? { ...@@ -81,7 +82,7 @@ internal fun DataInputStream.readMessage(): Message? {
println(value.size) println(value.size)
println(value.toUHexString()) println(value.toUHexString())
//todo 未知压缩算法 //todo 未知压缩算法
this.skip(7)//几个TLV this.discardExact(7)//几个TLV
return PlainText(String(value)) return PlainText(String(value))
} }
...@@ -92,21 +93,21 @@ internal fun DataInputStream.readMessage(): Message? { ...@@ -92,21 +93,21 @@ internal fun DataInputStream.readMessage(): Message? {
else -> { else -> {
println("未知的messageType=0x${messageType.toByte().toUHexString()}") println("未知的messageType=0x${messageType.toByte().toUHexString()}")
println("后文=${this.readAllBytes().toUHexString()}") println("后文=${this.readBytes().toUHexString()}")
null null
} }
} }
} }
fun DataInputStream.readMessageChain(): MessageChain { fun ByteReadPacket.readMessageChain(): MessageChain {
val chain = MessageChain() val chain = MessageChain()
var got: Message? = null var got: Message? = null
do { do {
if (got != null) { if (got != null) {
chain.concat(got) chain.concat(got)
} }
if (this.available() == 0) { if (this.remaining == 0L) {
return chain return chain
} }
got = this.readMessage() got = this.readMessage()
...@@ -114,34 +115,34 @@ fun DataInputStream.readMessageChain(): MessageChain { ...@@ -114,34 +115,34 @@ fun DataInputStream.readMessageChain(): MessageChain {
return chain return chain
} }
fun MessageChain.toByteArray(): ByteArray = dataEncode { result -> fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
this@toByteArray.list.forEach { message -> this@toPacket.list.forEach { message ->
result.write(with(message) { writePacket(with(message) {
when (this) { when (this) {
is Face -> dataEncode { section -> is Face -> buildPacket {
section.writeByte(MessageType.FACE.intValue) writeUByte(MessageType.FACE.value)
section.writeLVByteArray(dataEncode { child -> writeLVPacket {
child.writeShort(1) writeShort(1)
child.writeByte(this.id.id) 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) writeShort(2)
child.writeByte(0x14)//?? writeByte(0x14)//??
child.writeByte(this.id.id + 65) writeUByte((id.id + 65u).toUByte())
}) }
} }
is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported") is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported")
is Image -> dataEncode { section -> is Image -> buildPacket {
section.writeByte(MessageType.IMAGE.intValue) writeUByte(MessageType.IMAGE.value)
section.writeLVByteArray(dataEncode { child -> writeLVPacket {
child.writeByte(0x02) writeByte(0x02)
child.writeLVString(this.imageId) writeLVString(imageId)
child.writeHex("04 00 " + writeHex("04 00 " +
"04 9B 53 B0 08 " + "04 9B 53 B0 08 " +
"05 00 " + "05 00 " +
"04 D9 8A 5A 70 " + "04 D9 8A 5A 70 " +
...@@ -149,19 +150,19 @@ fun MessageChain.toByteArray(): ByteArray = dataEncode { result -> ...@@ -149,19 +150,19 @@ fun MessageChain.toByteArray(): ByteArray = dataEncode { result ->
"04 00 00 00 50 " + "04 00 00 00 50 " +
"07 00 " + "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") "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") 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) writeStringUtf8(imageId)
child.writeByte(0x41) writeByte(0x41)
}) }
} }
is PlainText -> dataEncode { section -> is PlainText -> buildPacket {
section.writeByte(MessageType.PLAIN_TEXT.intValue) writeUByte(MessageType.PLAIN_TEXT.value)
section.writeLVByteArray(dataEncode { child -> writeLVPacket {
child.writeByte(0x01) writeByte(0x01)
child.writeLVString(this.stringValue) writeLVString(stringValue)
}) }
} }
else -> throw UnsupportedOperationException("${this::class.simpleName} is not supported") else -> throw UnsupportedOperationException("${this::class.simpleName} is not supported")
......
package net.mamoe.mirai.network 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.BotSocket
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.handler.MessagePacketHandler import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.network.protocol.tim.packet.Packet import net.mamoe.mirai.utils.MiraiDatagramChannel
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
/** /**
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务. * Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
...@@ -20,42 +20,49 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState ...@@ -20,42 +20,49 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
* - [BotSocket]: 处理数据包底层的发送([ByteArray]) * - [BotSocket]: 处理数据包底层的发送([ByteArray])
* - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理 * - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理
* *
* 其中, [PacketHandler] 由 4 个子模块构成: * 其中, [PacketHandler] 由 3 个子模块构成:
* - [DebugPacketHandler] 输出 [Packet.toString] * - [LoginHandler] 处理 sendTouch/login/verification code 相关
* - [LoginHandler] 处理 touch/login/verification code 相关 * - [EventPacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [MessagePacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等) * - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等)
* *
* A BotNetworkHandler is used to connect with Tencent servers. * 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 val action: ActionPacketHandler
/** /**
* 尝试登录 * [PacketHandler] 列表
*/ */
suspend fun login(): LoginState val packetHandlers: PacketHandlerList
/**
* 尝试登录. 将会依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
*/
suspend fun login(configuration: LoginConfiguration): LoginResult
/** /**
* 添加一个临时包处理器 * 添加一个临时包处理器
...@@ -64,5 +71,12 @@ interface BotNetworkHandler { ...@@ -64,5 +71,12 @@ interface BotNetworkHandler {
*/ */
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>) 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 package net.mamoe.mirai.network
import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket 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.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket 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.ServerPacket
import net.mamoe.mirai.utils.getGTK import net.mamoe.mirai.utils.getGTK
import kotlin.jvm.JvmSynthetic
/** /**
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey. * 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
...@@ -16,8 +18,9 @@ import net.mamoe.mirai.utils.getGTK ...@@ -16,8 +18,9 @@ import net.mamoe.mirai.utils.getGTK
*/ */
class LoginSession( class LoginSession(
val bot: Bot, val bot: Bot,
val sessionKey: ByteArray, val sessionKey: ByteArray,//TODO 协议抽象? 可能并不是所有协议均需要 sessionKey
val socket: DataPacketSocket val socket: DataPacketSocket,
val scope: CoroutineScope
) { ) {
/** /**
...@@ -28,6 +31,7 @@ class LoginSession( ...@@ -28,6 +31,7 @@ class LoginSession(
/** /**
* Web api 使用 * Web api 使用
*/ */
@ExperimentalStdlibApi
var sKey: String = "" var sKey: String = ""
set(value) { set(value) {
field = value field = value
...@@ -39,6 +43,7 @@ class LoginSession( ...@@ -39,6 +43,7 @@ class LoginSession(
*/ */
var gtk: Int = 0 var gtk: Int = 0
val isOpen: Boolean get() = socket.isOpen
/** /**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket]. * 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
...@@ -56,7 +61,7 @@ class LoginSession( ...@@ -56,7 +61,7 @@ class LoginSession(
* @param P 期待的包 * @param P 期待的包
* @param handlerTemporary 处理器. * @param handlerTemporary 处理器.
*/ */
@JvmSynthetic //@JvmSynthetic
suspend inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableDeferred<Unit> { suspend inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableDeferred<Unit> {
val deferred = CompletableDeferred<Unit>() val deferred = CompletableDeferred<Unit>()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary)) this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary))
...@@ -88,4 +93,7 @@ class LoginSession( ...@@ -88,4 +93,7 @@ class LoginSession(
}) })
return deferred return deferred
} }
} }
\ No newline at end of file
suspend fun LoginSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet)
\ No newline at end of file
...@@ -2,17 +2,9 @@ ...@@ -2,17 +2,9 @@
package net.mamoe.mirai.network.protocol.tim package net.mamoe.mirai.network.protocol.tim
import net.mamoe.mirai.utils.TEA import net.mamoe.mirai.utils.solveIpAddress
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import java.net.InetAddress
import java.util.*
import java.util.stream.Collectors
/**
* @author Him188moe
*/
object TIMProtocol { object TIMProtocol {
val SERVER_IP: List<String> = { val SERVER_IP: List<String> = {
//add("183.60.56.29") //add("183.60.56.29")
...@@ -25,7 +17,7 @@ object TIMProtocol { ...@@ -25,7 +17,7 @@ object TIMProtocol {
"sz8.tencent.com", "sz8.tencent.com",
"sz9.tencent.com", "sz9.tencent.com",
"sz2.tencent.com" "sz2.tencent.com"
).forEach { list.add(InetAddress.getByName(it).hostAddress) } ).forEach { list.add(solveIpAddress(it)) }
list.toList() list.toList()
}() }()
...@@ -45,7 +37,7 @@ object TIMProtocol { ...@@ -45,7 +37,7 @@ object TIMProtocol {
const val constantData2 = "00 00 04 53 00 00 00 01 00 00 15 85 " 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 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 { ...@@ -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 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 的算法计算结果 * 并非常量. 是 publicKey 与 key0836 的算法计算结果
*/ */
...@@ -100,27 +87,4 @@ object TIMProtocol { ...@@ -100,27 +87,4 @@ object TIMProtocol {
*/ */
const val messageConst1 = "00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91" 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 // 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 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.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientAccountInfoRequestPacket import net.mamoe.mirai.network.protocol.tim.packet.*
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.action.AddFriendResult 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.ClientAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientCanAddFriendPacket 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.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.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.getGTK import net.mamoe.mirai.utils.getGTK
import java.awt.image.BufferedImage import net.mamoe.mirai.utils.hexToBytes
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
/** /**
* 动作: 获取好友列表, 点赞, 踢人等. * 动作: 获取好友列表, 点赞, 踢人等.
...@@ -32,12 +23,13 @@ import java.util.function.Supplier ...@@ -32,12 +23,13 @@ import java.util.function.Supplier
* @author Him188moe * @author Him188moe
*/ */
class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>()) private val addFriendSessions = mutableListOf<AddFriendSession>()
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>()) private val uploadImageSessions = mutableListOf<UploadImageSession>()
private var sKeyRefresherFuture: ScheduledFuture<*>? = null private var sKeyRefresherJob: Job? = null
@ExperimentalStdlibApi
override suspend fun onPacketReceived(packet: ServerPacket) { override suspend fun onPacketReceived(packet: ServerPacket) {
when (packet) { when (packet) {
is ServerCanAddFriendResponsePacket -> { is ServerCanAddFriendResponsePacket -> {
...@@ -65,11 +57,13 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -65,11 +57,13 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
session.sKey = packet.sKey session.sKey = packet.sKey
session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.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)) session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey))
} }
}, 1800000, 1800000, TimeUnit.MILLISECONDS) }
session.gtk = getGTK(session.sKey) session.gtk = getGTK(session.sKey)
} }
...@@ -82,15 +76,9 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -82,15 +76,9 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
} }
} }
//@JvmSynthetic
suspend fun addFriend(qqNumber: Long, message: Supplier<String>) { suspend fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableDeferred<AddFriendResult> {
addFriend(qqNumber, lazy { message.get() }) val future = CompletableDeferred<AddFriendResult>()
}
@JvmSynthetic
suspend fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
val future = CompletableFuture<AddFriendResult>()
val session = AddFriendSession(qqNumber, future, message) val session = AddFriendSession(qqNumber, future, message)
// uploadImageSessions.add(session) // uploadImageSessions.add(session)
session.sendAddRequest() session.sendAddRequest()
...@@ -108,14 +96,14 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -108,14 +96,14 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
} }
override fun close() { override fun close() {
this.sKeyRefresherFuture?.cancel(true) this.sKeyRefresherJob?.cancel()
this.sKeyRefresherFuture = null this.sKeyRefresherJob = null
} }
private inner class UploadImageSession( private inner class UploadImageSession(
private val group: Long, private val group: Long,
private val future: CompletableFuture<AddFriendResult>, private val future: CompletableDeferred<AddFriendResult>
private val image: BufferedImage //private val image: BufferedImage
) { ) {
lateinit var id: ByteArray lateinit var id: ByteArray
...@@ -167,7 +155,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -167,7 +155,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private inner class AddFriendSession( private inner class AddFriendSession(
private val qq: Long, private val qq: Long,
private val future: CompletableFuture<AddFriendResult>, private val future: CompletableDeferred<AddFriendResult>,
private val message: Lazy<String> private val message: Lazy<String>
) { ) {
lateinit var id: ByteArray lateinit var id: ByteArray
...@@ -180,7 +168,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -180,7 +168,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
when (packet) { when (packet) {
is ServerCanAddFriendResponsePacket -> { is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) { if (!(packet.idByteArray.contentEquals(id))) {
return return
} }
...@@ -211,7 +199,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -211,7 +199,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
suspend fun sendAddRequest() { 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() { fun close() {
......
package net.mamoe.mirai.network.protocol.tim.handler package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.io.core.Closeable
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.network.LoginSession import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler 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.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket 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 ...@@ -14,9 +16,21 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
* *
* @author Him188moe * @author Him188moe
*/ */
interface DataPacketSocket { interface DataPacketSocket : Closeable {
val owner: Bot val owner: Bot
val serverIp: String
/**
* UDP 通道
*/
val channel: MiraiDatagramChannel
/**
* 是否开启
*/
val isOpen: Boolean
/** /**
* 分发数据包给 [PacketHandler] * 分发数据包给 [PacketHandler]
*/ */
...@@ -31,7 +45,5 @@ interface DataPacketSocket { ...@@ -31,7 +45,5 @@ interface DataPacketSocket {
*/ */
suspend fun sendPacket(packet: ClientPacket) suspend fun sendPacket(packet: ClientPacket)
fun isClosed(): Boolean override fun close()
fun close()
} }
\ No newline at end of file
...@@ -9,14 +9,13 @@ import net.mamoe.mirai.getGroupByNumber ...@@ -9,14 +9,13 @@ import net.mamoe.mirai.getGroupByNumber
import net.mamoe.mirai.getQQ import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.MessageChain import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.LoginSession import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerFriendMessageEventPacket import net.mamoe.mirai.network.distributePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupMessageEventPacket import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupUploadFileEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientSendFriendMessagePacket 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.ClientSendGroupMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket 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.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.utils.MiraiLogger
/** /**
* 处理消息事件, 承担消息发送任务. * 处理消息事件, 承担消息发送任务.
...@@ -24,10 +23,10 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessage ...@@ -24,10 +23,10 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessage
* @author Him188moe * @author Him188moe
*/ */
@Suppress("EXPERIMENTAL_API_USAGE") @Suppress("EXPERIMENTAL_API_USAGE")
class MessagePacketHandler(session: LoginSession) : PacketHandler(session) { class EventPacketHandler(session: LoginSession) : PacketHandler(session) {
internal var ignoreMessage: Boolean = true internal var ignoreMessage: Boolean = true
override suspend fun onPacketReceived(packet: ServerPacket) { override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) {
when (packet) { when (packet) {
is ServerGroupUploadFileEventPacket -> { is ServerGroupUploadFileEventPacket -> {
//todo //todo
...@@ -36,21 +35,28 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) { ...@@ -36,21 +35,28 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
is ServerFriendMessageEventPacket -> { is ServerFriendMessageEventPacket -> {
if (ignoreMessage) return 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 -> { is ServerGroupMessageEventPacket -> {
if (ignoreMessage) return 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 ServerSendFriendMessageResponsePacket,
is ServerSendGroupMessageResponsePacket -> { is ServerSendGroupMessageResponsePacket -> {
//ignored //ignored
} }
is ServerFieldOnlineStatusChangedPacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
is ServerFieldOnlineStatusChangedPacket -> {
MiraiLogger.logInfo("${packet.qq.toLong()} 登录状态改变为 ${packet.status}")
//TODO
}
else -> { else -> {
//ignored //ignored
} }
......
...@@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.handler ...@@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.handler
import net.mamoe.mirai.network.LoginSession import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import kotlin.reflect.KClass
/** /**
* 数据包(接受/发送)处理器 * 数据包(接受/发送)处理器
...@@ -17,17 +18,18 @@ abstract class PacketHandler( ...@@ -17,17 +18,18 @@ abstract class PacketHandler(
} }
class PacketHandlerNode<T : PacketHandler>( class PacketHandlerNode<T : PacketHandler>(
val clazz: Class<T>, val clazz: KClass<T>,
val instance: T val instance: T
) )
fun PacketHandler.asNode(): PacketHandlerNode<PacketHandler> { fun <T : PacketHandler> T.asNode(): PacketHandlerNode<T> {
return PacketHandlerNode(this.javaClass, this) @Suppress("UNCHECKED_CAST")
return PacketHandlerNode(this::class as KClass<T>, this)
} }
class PacketHandlerList : MutableList<PacketHandlerNode<*>> by mutableListOf() { 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 { this.forEach {
if (it.clazz == clazz) { if (it.clazz == clazz) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
......
...@@ -4,7 +4,6 @@ import kotlinx.coroutines.CompletableDeferred ...@@ -4,7 +4,6 @@ import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.network.LoginSession import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket 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.ServerPacket
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
...@@ -20,7 +19,7 @@ import kotlin.reflect.KClass ...@@ -20,7 +19,7 @@ import kotlin.reflect.KClass
* *
* @see LoginSession.expectPacket * @see LoginSession.expectPacket
*/ */
open class TemporaryPacketHandler<P : ServerPacket>( class TemporaryPacketHandler<P : ServerPacket>(
private val expectationClass: KClass<P>, private val expectationClass: KClass<P>,
private val deferred: CompletableDeferred<Unit>, private val deferred: CompletableDeferred<Unit>,
private val fromSession: LoginSession 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 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 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( ...@@ -14,13 +22,13 @@ class ClientAccountInfoRequestPacket(
private val qq: Long, private val qq: Long,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)//part of packet id this.writeRandom(2)
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2) this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) { this.encryptAndWrite(sessionKey) {
writeByte(0x88) writeUByte(0x88.toUByte())
writeQQ(qq) writeQQ(qq)
writeByte(0x00) writeByte(0x00)
} }
...@@ -28,7 +36,7 @@ class ClientAccountInfoRequestPacket( ...@@ -28,7 +36,7 @@ class ClientAccountInfoRequestPacket(
} }
@PacketId("00 5C") @PacketId("00 5C")
class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(input) { class ServerAccountInfoResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
//等级 //等级
//升级剩余活跃天数 //升级剩余活跃天数
//ignored //ignored
...@@ -37,7 +45,7 @@ class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(inp ...@@ -37,7 +45,7 @@ class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(inp
} }
@PacketId("00 5C") @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) 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 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 net.mamoe.mirai.network.protocol.tim.TIMProtocol
import java.io.DataInputStream import net.mamoe.mirai.utils.*
import java.io.IOException
/**
* @author Him188moe
*/
@PacketId("00 58") @PacketId("00 58")
class ClientHeartbeatPacket( class ClientHeartbeatPacket(
private val qq: Long, private val qq: Long,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
@Throws(IOException::class) override val idHex: String by lazy {
override fun encode() { super.idHex + " " + getRandomByteArray(2).toUHexString()
this.writeRandom(2) }
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer) this.writeHex(TIMProtocol.fixVer)
this.encryptAndWrite(sessionKey) { this.encryptAndWrite(sessionKey) {
...@@ -24,4 +24,4 @@ class ClientHeartbeatPacket( ...@@ -24,4 +24,4 @@ class ClientHeartbeatPacket(
} }
} }
class ServerHeartbeatResponsePacket(input: DataInputStream) : ServerPacket(input) class ServerHeartbeatResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file \ 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 package net.mamoe.mirai.network.protocol.tim.packet
/**
* @author Him188moe
*/
@Retention(AnnotationRetention.RUNTIME) @Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE) @Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
annotation class PacketId( annotation class PacketId(
......
@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 @@ ...@@ -2,12 +2,15 @@
package net.mamoe.mirai.network.protocol.tim.packet.action 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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.utils.getRandomByteArray import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.DataInputStream import net.mamoe.mirai.network.protocol.tim.packet.setId
import java.util.* import net.mamoe.mirai.utils.*
/** /**
* 向服务器检查是否可添加某人为好友 * 向服务器检查是否可添加某人为好友
...@@ -20,15 +23,11 @@ class ClientCanAddFriendPacket( ...@@ -20,15 +23,11 @@ class ClientCanAddFriendPacket(
val qq: Long, val qq: Long,
val sessionKey: ByteArray val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
val packetIdLast = getRandomByteArray(2) override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
} }
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.write(packetIdLast)//id, 2bytes
this.writeQQ(bot) this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2) this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) { this.encryptAndWrite(sessionKey) {
...@@ -38,7 +37,7 @@ class ClientCanAddFriendPacket( ...@@ -38,7 +37,7 @@ class ClientCanAddFriendPacket(
} }
@PacketId("00 A7") @PacketId("00 A7")
class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(input) { class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var state: State lateinit var state: State
enum class State { enum class State {
...@@ -50,7 +49,8 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in ...@@ -50,7 +49,8 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
override fun decode() { override fun decode() {
val data = input.goto(0).readAllBytes() input.readBytes()
val data = input.readRemainingBytes()
if (data.size == 99) { if (data.size == 99) {
state = State.ALREADY_ADDED state = State.ALREADY_ADDED
return return
...@@ -61,13 +61,13 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in ...@@ -61,13 +61,13 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
0x99u -> State.ALREADY_ADDED 0x99u -> State.ALREADY_ADDED
0x03u, 0x03u,
0x04u -> State.FAILED 0x04u -> State.FAILED
else -> throw IllegalArgumentException(Arrays.toString(data)) else -> throw IllegalArgumentException(data.contentToString())
} }
} }
@PacketId("00 A7") @PacketId("00 A7")
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) { class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerCanAddFriendResponsePacket { fun decrypt(sessionKey: ByteArray): ServerCanAddFriendResponsePacket {
return ServerCanAddFriendResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex) return ServerCanAddFriendResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
} }
...@@ -84,15 +84,11 @@ class ClientAddFriendPacket( ...@@ -84,15 +84,11 @@ class ClientAddFriendPacket(
val qq: Long, val qq: Long,
val sessionKey: ByteArray val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
val packetIdLast = getRandomByteArray(2) override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
} }
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.write(packetIdLast)//id, 2bytes
this.writeQQ(bot) this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2) this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) { this.encryptAndWrite(sessionKey) {
...@@ -104,17 +100,17 @@ class ClientAddFriendPacket( ...@@ -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() { override fun decode() {
...@@ -125,7 +121,7 @@ abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPa ...@@ -125,7 +121,7 @@ abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPa
TODO() TODO()
} }
class Encrypted(input: DataInputStream) : ServerPacket(input) { class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey)) fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey))
} }
} }
......
package net.mamoe.mirai.network.protocol.tim.packet.action package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.*
import net.mamoe.mirai.message.MessageChain 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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.utils.dataEncode import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import java.io.DataInputStream import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.*
/**
* @author Him188moe
*/
@PacketId("00 CD") @PacketId("00 CD")
class ClientSendFriendMessagePacket( class ClientSendFriendMessagePacket(
private val botQQ: Long, private val botQQ: Long,
...@@ -17,8 +17,8 @@ class ClientSendFriendMessagePacket( ...@@ -17,8 +17,8 @@ class ClientSendFriendMessagePacket(
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val message: MessageChain private val message: MessageChain
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)//part of packet id this.writeRandom(2)
this.writeQQ(botQQ) this.writeQQ(botQQ)
this.writeHex(TIMProtocol.fixVer2) this.writeHex(TIMProtocol.fixVer2)
...@@ -30,7 +30,7 @@ class ClientSendFriendMessagePacket( ...@@ -30,7 +30,7 @@ class ClientSendFriendMessagePacket(
writeHex("37 0F")//TIM最新: 38 03 writeHex("37 0F")//TIM最新: 38 03
writeQQ(botQQ) writeQQ(botQQ)
writeQQ(targetQQ) writeQQ(targetQQ)
write(md5(dataEncode { md5Key -> md5Key.writeQQ(targetQQ); md5Key.write(sessionKey) })) writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
writeHex("00 0B") writeHex("00 0B")
writeRandom(2) writeRandom(2)
writeTime() writeTime()
...@@ -54,11 +54,11 @@ class ClientSendFriendMessagePacket( ...@@ -54,11 +54,11 @@ class ClientSendFriendMessagePacket(
writeHex(TIMProtocol.messageConst1)//... 85 E9 BB 91 writeHex(TIMProtocol.messageConst1)//... 85 E9 BB 91
writeZero(2) writeZero(2)
write(message.toByteArray()) writePacket(message.toPacket())
/* /*
//Plain text //Plain text
val bytes = message.toByteArray() val bytes = event.toPacket()
it.writeByte(0x01) it.writeByte(0x01)
it.writeShort(bytes.size + 3) it.writeShort(bytes.size + 3)
it.writeByte(0x01) it.writeByte(0x01)
...@@ -69,4 +69,4 @@ class ClientSendFriendMessagePacket( ...@@ -69,4 +69,4 @@ class ClientSendFriendMessagePacket(
} }
@PacketId("00 CD") @PacketId("00 CD")
class ServerSendFriendMessageResponsePacket(input: DataInputStream) : ServerPacket(input) class ServerSendFriendMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file \ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.action 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.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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.utils.dataEncode import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.DataInputStream import net.mamoe.mirai.utils.*
/**
* @author Him188moe
*/
@PacketId("00 02") @PacketId("00 02")
class ClientSendGroupMessagePacket( class ClientSendGroupMessagePacket(
private val botQQ: Long, private val botQQ: Long,
...@@ -18,39 +18,35 @@ class ClientSendGroupMessagePacket( ...@@ -18,39 +18,35 @@ class ClientSendGroupMessagePacket(
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val message: MessageChain private val message: MessageChain
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)//part of packet id this.writeRandom(2)
this.writeQQ(botQQ) this.writeQQ(botQQ)
this.writeHex(TIMProtocol.fixVer2) this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) { this.encryptAndWrite(sessionKey) {
val bytes = message.toByteArray()
writeByte(0x2A) writeByte(0x2A)
writeGroup(groupId) writeGroup(groupId)
writeLVByteArray(dataEncode { child -> writeLVPacket {
child.writeHex("00 01 01") writeHex("00 01 01")
child.writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00") writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
child.writeTime() writeTime()
child.writeRandom(4) writeRandom(4)
child.writeHex("00 00 00 00 09 00 86") writeHex("00 00 00 00 09 00 86")
child.writeHex(TIMProtocol.messageConst1) writeHex(TIMProtocol.messageConst1)
child.writeZero(2) writeZero(2)
//messages writePacket(message.toPacket())
child.write(bytes) }
})
/*it.writeByte(0x01) /*it.writeByte(0x01)
it.writeShort(bytes.size + 3) it.writeShort(bytes.size + 3)
it.writeByte(0x01) it.writeByte(0x01)
it.writeShort(bytes.size) it.writeShort(bytes.size)
it.write(bytes)*/ it.write(bytes)*/
println(toByteArray().toUHexString())
} }
} }
} }
@PacketId("00 02") @PacketId("00 02")
class ServerSendGroupMessageResponsePacket(input: DataInputStream) : ServerPacket(input) class ServerSendGroupMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file \ No newline at end of file
...@@ -2,32 +2,32 @@ ...@@ -2,32 +2,32 @@
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.utils.ClientLoginStatus import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.*
/** /**
* 改变在线状态: "我在线上", "隐身" 等 * 改变在线状态: "我在线上", "隐身" 等
*
* @author Him188moe
*/ */
@PacketId("00 EC") @PacketId("00 EC")
class ClientChangeOnlineStatusPacket( class ClientChangeOnlineStatusPacket(
private val qq: Long, private val qq: Long,
private val sessionKey: ByteArray, private val sessionKey: ByteArray,
private val loginStatus: ClientLoginStatus private val loginStatus: OnlineStatus
) : ClientPacket() { ) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)//part of packet id
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2) this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) { this.encryptAndWrite(sessionKey) {
writeHex("01 00") writeHex("01 00")
writeByte(loginStatus.id.toInt()) writeUByte(loginStatus.id)
writeHex("00 01 00 01 00 04 00 00 00 00") writeHex("00 01 00 01 00 04 00 00 00 00")
} }
} }
......
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.utils.TEA import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.Tested import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.hexToBytes
import java.io.DataOutputStream
/** /**
* Password submission (0836_622) * Password submission (0836_622)
*
* @author Him188moe
*/ */
@PacketId("08 36 31 03") @PacketId("08 36 31 03")
@Tested @Tested
...@@ -21,10 +20,11 @@ class ClientPasswordSubmissionPacket( ...@@ -21,10 +20,11 @@ class ClientPasswordSubmissionPacket(
private val loginTime: Int, private val loginTime: Int,
private val loginIP: String, private val loginIP: String,
private val privateKey: ByteArray,//16 random by client 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() { ) : ClientPacket() {
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.passwordSubmissionTLV1) this.writeHex(TIMProtocol.passwordSubmissionTLV1)
...@@ -36,7 +36,7 @@ class ClientPasswordSubmissionPacket( ...@@ -36,7 +36,7 @@ class ClientPasswordSubmissionPacket(
//TODO shareKey 极大可能为 publicKey, key0836 计算得到 //TODO shareKey 极大可能为 publicKey, key0836 计算得到
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) { this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
writePart1(qq, password, loginTime, loginIP, privateKey, token0825) writePart1(qq, password, loginTime, loginIP, privateKey, token0825, randomDeviceName)
writePart2() writePart2()
} }
} }
...@@ -46,16 +46,13 @@ class ClientPasswordSubmissionPacket( ...@@ -46,16 +46,13 @@ class ClientPasswordSubmissionPacket(
//但为简化处理, 特固定这个 id //但为简化处理, 特固定这个 id
@PacketId("08 36 31 04") @PacketId("08 36 31 04")
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null) 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)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
@PacketId("08 36 31 05") @PacketId("08 36 31 05")
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray) 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)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, null)
@PacketId("08 36 31 06") @PacketId("08 36 31 06")
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null) 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)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
open class ClientLoginResendPacket constructor( open class ClientLoginResendPacket constructor(
...@@ -66,9 +63,10 @@ open class ClientLoginResendPacket constructor( ...@@ -66,9 +63,10 @@ open class ClientLoginResendPacket constructor(
private val privateKey: ByteArray, private val privateKey: ByteArray,
private val token0825: ByteArray, private val token0825: ByteArray,
private val token00BA: ByteArray, private val token00BA: ByteArray,
private val tlv0006: ByteArray? = null private val randomDeviceName: Boolean = false,
private val tlv0006: IoBuffer? = null
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.passwordSubmissionTLV1) this.writeHex(TIMProtocol.passwordSubmissionTLV1)
...@@ -79,13 +77,14 @@ open class ClientLoginResendPacket constructor( ...@@ -79,13 +77,14 @@ open class ClientLoginResendPacket constructor(
this.writeHex(TIMProtocol.key0836)//16 this.writeHex(TIMProtocol.key0836)//16
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) { 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("01 10")
writeHex("00 3C")//length writeHex("00 3C")
writeHex("00 01")//tag writeHex("00 01")
writeHex("00 38")//length
write(token00BA)//value writeHex("00 38")
writeFully(token00BA)
writePart2() writePart2()
} }
...@@ -93,32 +92,40 @@ open class ClientLoginResendPacket constructor( ...@@ -93,32 +92,40 @@ open class ClientLoginResendPacket constructor(
} }
/** private fun BytePacketBuilder.writePart1(
* @author Him188moe qq: Long,
*/ password: String,
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) { loginTime: Int,
loginIP: String,
privateKey: ByteArray,
token0825: ByteArray,
randomDeviceName: Boolean,
tlv0006: IoBuffer? = null
) {
//this.writeInt(System.currentTimeMillis().toInt()) //this.writeInt(System.currentTimeMillis().toInt())
this.writeHex("01 12")//tag this.writeHex("01 12")//tag
this.writeHex("00 38")//length this.writeHex("00 38")//length
this.write(token0825)//length this.writeFully(token0825)//length
this.writeHex("03 0F")//tag this.writeHex("03 0F")//tag
this.writeDeviceName(false) this.writeDeviceName(randomDeviceName)
this.writeHex("00 05 00 06 00 02") this.writeHex("00 05 00 06 00 02")
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex("00 06")//tag this.writeHex("00 06")//tag
this.writeHex("00 78")//length this.writeHex("00 78")//length
if (tlv0006 != null) { if (tlv0006 != null) {
this.write(tlv0006) MiraiLogger.logDebug("tlv0006!=null")
this.writeFully(tlv0006)
} else { } else {
MiraiLogger.logDebug("tlv0006==null")
this.writeTLV0006(qq, password, loginTime, loginIP, privateKey) this.writeTLV0006(qq, password, loginTime, loginIP, privateKey)
} }
//fix //fix
this.writeHex(TIMProtocol.passwordSubmissionTLV2) this.writeHex(TIMProtocol.passwordSubmissionTLV2)
this.writeHex("00 1A")//tag this.writeHex("00 1A")//tag
this.writeHex("00 40")//length 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.constantData1)
this.writeHex(TIMProtocol.constantData2) this.writeHex(TIMProtocol.constantData2)
this.writeQQ(qq) this.writeQQ(qq)
...@@ -133,7 +140,7 @@ private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: I ...@@ -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("03 12")//tag
this.writeHex("00 05")//length this.writeHex("00 05")//length
...@@ -161,9 +168,8 @@ private fun DataOutputStream.writePart2() { ...@@ -161,9 +168,8 @@ private fun DataOutputStream.writePart2() {
//value //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("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 this.writeHex("00 14")
writeCRC32() this.writeCRC32()
} }
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
/** enum class LoginResult {
* @author Him188moe
*/
enum class LoginState {
/** /**
* 登录成功 * 登录成功
*/ */
...@@ -39,6 +36,11 @@ enum class LoginState { ...@@ -39,6 +36,11 @@ enum class LoginState {
*/ */
UNKNOWN, UNKNOWN,
/**
* 未知. 更换服务器或等几分钟再登录可能解决.
*/
INTERNAL_ERROR,
/** /**
* 超时 * 超时
*/ */
......
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.network.protocol.tim.TIMProtocol import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import java.io.DataInputStream 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 * SKey 用于 http api
*
* @author Him188moe
*/ */
@PacketId("00 1D") @PacketId("00 1D")
class ClientSKeyRequestPacket( class ClientSKeyRequestPacket(
private val qq: Long, private val qq: Long,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override val idHex: String by lazy {
this.writeRandom(2)//part of packet id super.idHex + " " + getRandomByteArray(2).toUHexString()
}
this.writeQQ(qq) override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeHex(TIMProtocol.fixVer2) writeQQ(qq)
this.encryptAndWrite(sessionKey) { 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") 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") @PacketId("00 1D")
class ClientSKeyRefreshmentRequestPacket( class ClientSKeyRefreshmentRequestPacket(
private val qq: Long, private val qq: Long,
private val sessionKey: ByteArray private val sessionKey: ByteArray
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override val idHex: String by lazy {
this.writeRandom(2)//part of packet id super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq) this.writeQQ(qq)
this.encryptAndWrite(sessionKey) { 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") 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( ...@@ -45,18 +48,16 @@ class ClientSKeyRefreshmentRequestPacket(
} }
} }
/** class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
* @author Him188moe
*/
class ServerSKeyResponsePacket(input: DataInputStream) : ServerPacket(input) {
lateinit var sKey: String lateinit var sKey: String
override fun decode() { override fun decode() = with(input) {
this.sKey = String(this.input.goto(4).readNBytes(10)) discardExact(4)
sKey = this.readString(10)//todo test
MiraiLogger.logDebug("SKey=$sKey")
} }
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket = ServerSKeyResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex) 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 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.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.DataInputStream
/** /**
* Congratulations! * Congratulations!
...@@ -10,4 +10,4 @@ import java.io.DataInputStream ...@@ -10,4 +10,4 @@ import java.io.DataInputStream
* @author Him188moe * @author Him188moe
*/ */
@PacketId("00 EC") @PacketId("00 EC")
class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input) class ServerLoginSuccessPacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file \ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login 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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.utils.dataEncode import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import java.io.DataInputStream import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.net.InetAddress import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* @author Him188moe
*/
@PacketId("08 28 04 34") @PacketId("08 28 04 34")
class ClientSessionRequestPacket( class ClientSessionRequestPacket(
private val qq: Long, private val qq: Long,
private val serverIp: String, private val serverIp: String,
private val token38: ByteArray, private val token38: IoBuffer,
private val token88: ByteArray, private val token88: IoBuffer,
private val encryptionKey: ByteArray private val encryptionKey: IoBuffer
) : ClientPacket() { ) : ClientPacket() {
override fun encode() { override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A") this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A")
this.writeHex("00 38") this.writeHex("00 38")
this.write(token38) this.writeFully(token38)
this.encryptAndWrite(encryptionKey) { this.encryptAndWrite(encryptionKey) {
writeHex("00 07 00 88") 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") writeHex("00 0C 00 16 00 02 00 00 00 00 00 00 00 00 00 00")
writeIP(serverIp) writeIP(serverIp)
writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1 writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1
...@@ -56,44 +54,51 @@ class ClientSessionRequestPacket( ...@@ -56,44 +54,51 @@ class ClientSessionRequestPacket(
writeHex("68") writeHex("68")
writeHex("00 00 00 00 00 2D 00 06 00 01") 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") @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 sessionKey: ByteArray
lateinit var tlv0105: ByteArray lateinit var tlv0105: ByteReadPacket
@Tested
override fun decode() { override fun decode() = with(input) {
when (dataLength) { when (val dataLength = remaining) {
407 -> { 407L -> {
input.goto(25) input.discardExact(25)//todo test
sessionKey = input.readNBytes(16) sessionKey = input.readBytes(16)
} }
439 -> { 439L -> {
input.goto(63) input.discardExact(63)
sessionKey = input.readNBytes(16) sessionKey = input.readBytes(16)
} }
512, 502L,//?
527 -> { 512L,
input.goto(63) 527L -> {
sessionKey = input.readNBytes(16) 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
tlv0105 = dataEncode { sessionKey = input.readBytes(16)
it.writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00") tlv0105 = buildPacket {
input.goto(dataLength - 122) writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00")
it.write(input.readNBytes(56)) input.discardExact(input.remaining - 122 - 1)
it.writeHex("00 40 02 02 03 3C 01 03 00 00") writeFully(input.readIoBuffer(56))
input.goto(dataLength - 55) writeHex("00 40 02 02 03 3C 01 03 00 00")
it.write(input.readNBytes(56)) input.discardExact(11)
writeFully(input.readIoBuffer(56))
} //todo 这个 tlv0105似乎可以保存起来然后下次登录时使用. } //todo 这个 tlv0105似乎可以保存起来然后下次登录时使用.
/*
Discarded(63) =00 00 0D 00 06 00 01 00 00 00 00 00 1F 00 22 00 01 F7 AB 01 4B 23 B5 47 FC 79 02 09 E0 19 EF 61 91 14 AD 8F 38 2E 8B D7 47 39 DE FE 84 A7 E5 6E 3D 00 14 00 04 00 01 00 3C 01 0C 00 73 00 01
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()) else -> throw IllegalArgumentException(dataLength.toString())
...@@ -104,9 +109,8 @@ class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val d ...@@ -104,9 +109,8 @@ class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val d
} }
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) { class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionResponseDecryptionKey: ByteArray): ServerSessionKeyResponsePacket = this.decryptAsByteArray(sessionResponseDecryptionKey).let { fun decrypt(sessionResponseDecryptionKey: IoBuffer): ServerSessionKeyResponsePacket =
ServerSessionKeyResponsePacket(it.dataInputStream(), it.size).setId(this.idHex) ServerSessionKeyResponsePacket(this.decryptBy(sessionResponseDecryptionKey)).setId(this.idHex)
}
} }
} }
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.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.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.* import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.DataInputStream import net.mamoe.mirai.network.protocol.tim.packet.setId
import java.io.IOException import net.mamoe.mirai.utils.*
/** /**
* The packet received when logging in, used to redirect server address * The packet received when logging in, used to redirect server address
...@@ -17,7 +21,7 @@ import java.io.IOException ...@@ -17,7 +21,7 @@ import java.io.IOException
*/ */
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") @Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@PacketId("08 25 31 01") @PacketId("08 25 31 01")
class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) { class ServerTouchResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
var serverIP: String? = null var serverIP: String? = null
var loginTime: Int = 0 var loginTime: Int = 0
...@@ -30,28 +34,28 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp ...@@ -30,28 +34,28 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
} }
override fun decode() { override fun decode() = with(input) {
when (val id = input.readByte().toUByte().toInt()) { when (val id = readByte().toUByte().toInt()) {
0xFE -> { 0xFE -> {//todo 在 packet 解析时分类而不是在这里分类
input.skip(94) discardExact(94)
serverIP = input.readIP() serverIP = readIP()
} }
0x00 -> { 0x00 -> {
input.skip(4) discardExact(4)
token0825 = input.readNBytes(56) token0825 = readBytes(56)
input.skip(6) discardExact(6)
loginTime = input.readInt() loginTime = readInt()
loginIP = input.readIP() loginIP = readIP()
} }
else -> { 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) { fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) {
Type.TYPE_08_25_31_02 -> TIMProtocol.redirectionKey.hexToBytes() Type.TYPE_08_25_31_02 -> TIMProtocol.redirectionKey.hexToBytes()
...@@ -61,16 +65,13 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp ...@@ -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 * @author Him188moe
*/ */
@PacketId("08 25 31 01") @PacketId("08 25 31 01")
class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() { class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
@Throws(IOException::class)
override fun encode() {
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer) this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.touchKey) this.writeHex(TIMProtocol.touchKey)
...@@ -94,13 +95,11 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl ...@@ -94,13 +95,11 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
*/ */
@PacketId("08 25 31 02") @PacketId("08 25 31 02")
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() { class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
override fun encode() {
this.writeQQ(qq) this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer) this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.redirectionKey) this.writeHex(TIMProtocol.redirectionKey)
this.encryptAndWrite(TIMProtocol.redirectionKey) { this.encryptAndWrite(TIMProtocol.redirectionKey) {
this.writeHex(TIMProtocol.constantData1) this.writeHex(TIMProtocol.constantData1)
this.writeHex(TIMProtocol.constantData2) this.writeHex(TIMProtocol.constantData2)
......
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
/**
* @author Him188moe
*/
data class BotAccount( data class BotAccount(
val qqNumber: Long,//实际上是 UInt val qqNumber: Long,//实际上是 UInt
val password: String//todo 不保存 password? 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 ...@@ -2,7 +2,5 @@ package net.mamoe.mirai.utils
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
/**
* @author Him188moe
*/
class ContactList<C : Contact> : MutableMap<Long, C> by mutableMapOf() 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") @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
/** /**
...@@ -7,12 +8,21 @@ package net.mamoe.mirai.utils ...@@ -7,12 +8,21 @@ package net.mamoe.mirai.utils
* @author Him188moe * @author Him188moe
* @see net.mamoe.mirai.network.protocol.tim.packet.login.ClientChangeOnlineStatusPacket * @see net.mamoe.mirai.network.protocol.tim.packet.login.ClientChangeOnlineStatusPacket
*/ */
enum class ClientLoginStatus( enum class OnlineStatus(
// TODO: 2019/8/31 add more ClientLoginStatus
val id: UByte//1 ubyte 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
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
...@@ -5,13 +5,13 @@ import kotlinx.coroutines.Dispatchers ...@@ -5,13 +5,13 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.network.LoginSession import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.image.ClientTryGetImageIDPacket import net.mamoe.mirai.network.protocol.tim.packet.ClientTryGetImageIDPacketJvm
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDFailedPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDResponsePacket import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDSuccessPacket import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.network.protocol.tim.packet.md5
import net.mamoe.mirai.qqNumber import net.mamoe.mirai.qqNumber
import net.mamoe.mirai.utils.ImageNetworkUtils import net.mamoe.mirai.utils.ImageNetworkUtils
import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toByteArray import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.utils.toUHexString
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
...@@ -32,7 +32,7 @@ class UnsolvedImage(private val filename: String, val image: BufferedImage) { ...@@ -32,7 +32,7 @@ class UnsolvedImage(private val filename: String, val image: BufferedImage) {
suspend fun upload(session: LoginSession, contact: Contact): CompletableDeferred<Unit> { suspend fun upload(session: LoginSession, contact: Contact): CompletableDeferred<Unit> {
return session.expectPacket<ServerTryGetImageIDResponsePacket> { 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 { onExpect {
when (it) { 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
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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