Commit d89f8acc authored by Him188's avatar Him188

Multiplatform

parent c09d44f4
......@@ -4,6 +4,7 @@ ext {
// kotlin
kotlinJvm = "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
kotlinCommon = "org.jetbrains.kotlin:kotlin-stdlib-common:$kotlin_version"
kotlinNative = "org.jetbrains.kotlin:kotlin-stdlib-native:$kotlin_version"
// coroutine
coroutine_version = "1.3.0"
......@@ -13,6 +14,8 @@ ext {
coroutineAndroid = "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version"
coroutineJs = "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:$coroutine_version"
coroutineIo = "org.jetbrains.kotlinx:kotlinx-coroutines-io:0.24.0"
// reflect
reflect = "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
......@@ -25,5 +28,6 @@ ext {
kotlinxIOJvm = "org.jetbrains.kotlinx:kotlinx-io-jvm:$kotlinx_io_version"
kotlinxIOCommon = "org.jetbrains.kotlinx:kotlinx-io:$kotlinx_io_version"
kotlinxIOJS = "org.jetbrains.kotlinx:kotlinx-io-js:$kotlinx_io_version"
kotlinxIONative = "org.jetbrains.kotlinx:kotlinx-io-native:$kotlinx_io_version"
}
package net.mamoe.mirai
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.config.MiraiConfig
import net.mamoe.mirai.utils.setting.MiraiSettings
import java.io.File
import java.io.IOException
import java.util.concurrent.ExecutionException
/**
* @author Him188moe
*/
/**
* Mirai 服务器.
......@@ -40,11 +39,11 @@ object MiraiServer {
this.logger = MiraiLogger
logger.info("About to run Mirai (" + Mirai.VERSION + ") under " + if (isUnix) "unix" else "windows")
logger.info("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder)
logger.logInfo("About to run Mirai (" + Mirai.VERSION + ") under " + if (isUnix) "unix" else "windows")
logger.logInfo("Loading data under " + LoggerTextFormat.GREEN + this.parentFolder)
val setting = this.parentFolder + "/Mirai.ini"
logger.info("Selecting setting from " + LoggerTextFormat.GREEN + setting)
val setting = File(this.parentFolder, "/Mirai.ini")
logger.logInfo("Selecting setting from " + LoggerTextFormat.GREEN + setting)
/*
if (!setting.exists()) {
......@@ -54,7 +53,7 @@ object MiraiServer {
}
File qqs = new File(this.parentFolder + "/QQ.yml");
getLogger().info("Reading QQ accounts from " + LoggerTextFormat.GREEN + qqs);
getLogger().logInfo("Reading QQ accounts from " + LoggerTextFormat.GREEN + qqs);
if (!qqs.exists()) {
this.initQQConfig(qqs);
} else {
......@@ -67,10 +66,10 @@ object MiraiServer {
/*
MiraiSettingMapSection qqs = this.setting.getMapSection("qq");
qqs.forEach((a,p) -> {
this.getLogger().info("Finding available ports between " + "1-65536");
this.getLogger().logInfo("Finding available ports between " + "1-65536");
try {
int port = MiraiNetwork.getAvailablePort();
this.getLogger().info("Listening on port " + port);
this.getLogger().logInfo("Listening on port " + port);
} catch (IOException e) {
e.printStackTrace();
......@@ -84,18 +83,18 @@ object MiraiServer {
fun shutdown() {
if (this.enabled) {
logger.info("About to shutdown Mirai")
logger.info("Data have been saved")
logger.logInfo("About to shutdown Mirai")
logger.logInfo("Data have been saved")
}
}
private fun initSetting(setting: File) {
logger.info("Thanks for using Mirai")
logger.info("initializing Settings")
logger.logInfo("Thanks for using Mirai")
logger.logInfo("initializing Settings")
try {
if (setting.createNewFile()) {
logger.info("Mirai Config Created")
logger.logInfo("Mirai Config Created")
}
} catch (e: IOException) {
e.printStackTrace()
......@@ -113,24 +112,24 @@ object MiraiServer {
worker["core_task_pool_worker_amount"] = 5
val plugin = this.settings.getMapSection("plugin")
plugin["debug"] = false
plugin["logDebug"] = false
this.settings.save()
logger.info("initialized; changing can be made in setting file: $setting")
logger.logInfo("initialized; changing can be made in setting file: $setting")
}
private fun initQQConfig(qqConfig: File) {
this.qqs = MiraiConfig(qqConfig)
MiraiLogger.info("QQ account initialized; changing can be made in Config file: $qqConfig")
logger.info("QQ 账户管理初始化完毕")
MiraiLogger.logInfo("QQ account initialized; changing can be made in Config file: $qqConfig")
logger.logInfo("QQ 账户管理初始化完毕")
}
private fun reload() {
this.enabled = true
MiraiLogger.info(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai")
MiraiLogger.info("Mirai Version=" + Mirai.VERSION)
MiraiLogger.logInfo(LoggerTextFormat.GREEN.toString() + "Server enabled; Welcome to Mirai")
MiraiLogger.logInfo("Mirai Version=" + Mirai.VERSION)
MiraiLogger.info("Initializing [Bot]s")
MiraiLogger.logInfo("Initializing [Bot]s")
try {
availableBot
......@@ -142,23 +141,23 @@ object MiraiServer {
/*
this.qqs.keySet().stream().map(key -> this.qqs.getSection(key)).forEach(section -> {
getLogger().info("Initializing [Bot] " + section.getString("account"));
getLogger().logInfo("Initializing [Bot] " + section.getString("account"));
try {
Bot bot = new Bot(section);
var state = bot.network.login$mirai_core().of();
//bot.network.login$mirai_core().whenComplete((state, e) -> {
if (state == LoginState.SUCCESS) {
Bot.instances.add(bot);
getLogger().green(" Login Succeed");
getLogger().logGreen(" Login Succeed");
} else {
getLogger().error(" Login Failed with error " + state);
getLogger().logError(" Login Failed with logError " + state);
bot.close();
}
// }).of();
} catch (Throwable e) {
e.printStackTrace();
getLogger().error("Could not load QQ bots config!");
getLogger().logError("Could not load QQ bots config!");
System.exit(1);
}
});*/
......@@ -173,9 +172,9 @@ object MiraiServer {
get() {
for (it in qqList.split("\n").dropLastWhile { it.isEmpty() }.toTypedArray()) {
val strings = it.split("----").dropLastWhile { it.isEmpty() }.toTypedArray()
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), Console())
val bot = Bot(BotAccount(strings[0].toLong(), strings[1]), MiraiLogger)
if (runBlocking { bot.login() } === LoginState.SUCCESS) {
if (runBlocking { bot.login() } === LoginResult.SUCCESS) {
bot.green("Login succeed")
return bot
}
......
......@@ -4,22 +4,40 @@ apply plugin: "kotlin-multiplatform"
kotlin {
targets {
fromPreset(presets.jvm, "jvm")
//fromPreset(presets.mingwX64, "mingwX64")
}
jvm()
/*
mingwX64("mingwX64") {
binaries {
executable {
// Change to specify fully qualified name of your application's entry point:
entryPoint = 'main'
// Specify command-line arguments, if necessary:
runTask?.args('')
}
}
}*/
sourceSets {
commonMain {
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation rootProject.ext.kotlinCommon
implementation rootProject.ext.reflect
implementation rootProject.ext.coroutine
implementation rootProject.ext.kotlinJvm
//implementation rootProject.ext.coroutine
implementation rootProject.ext.coroutineCommon
implementation rootProject.ext.coroutineIo
implementation rootProject.ext.atomicFUCommon
implementation rootProject.ext.kotlinxIOCommon
}
}
jvmMain {
apply plugin: 'java'
......@@ -35,6 +53,23 @@ kotlin {
implementation 'org.ini4j:ini4j:0.5.2'
}
}
/*
mingwX64Main {
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation rootProject.ext.kotlinCommon
implementation rootProject.ext.coroutine
implementation rootProject.ext.coroutineNative
implementation rootProject.ext.kotlinNative
implementation rootProject.ext.reflect
//implementation rootProject.ext.coroutine
implementation rootProject.ext.kotlinxIONative
}
}*/
jvmTest {
}
......@@ -47,16 +82,3 @@ kotlin {
compileKotlinJvm {
}
\ No newline at end of file
/*
dependencies {
compile 'com.google.protobuf:protobuf-java:3.5.0'
compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0-M2'
compile 'io.netty:netty-all:4.1.38.Final'
compile 'net.java.dev.jna:jna:5.4.0'
compile 'org.apache.logging.log4j:log4j-core:2.12.1'
compile 'org.yaml:snakeyaml:1.18'
compile 'org.jetbrains.kotlin:kotlin-reflect:1.3.41'
compile 'org.jsoup:jsoup:1.12.1'
compile 'org.ini4j:ini4j:0.5.2'
}*/
package net.mamoe.mirai
import kotlinx.atomicfu.atomic
import kotlinx.coroutines.sync.Mutex
import net.mamoe.mirai.Bot.ContactSystem
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
......@@ -12,12 +14,12 @@ import net.mamoe.mirai.utils.MiraiLogger
/**
* Mirai 的机器人. 一个机器人实例登录一个 QQ 账号.
* Mirai 为多账号设计, 可同时维护多个机器人.
* <br></br>
*
* [Bot] 由 3 个模块组成.
* [联系人管理][ContactSystem]: 可通过 [Bot.contacts] 访问
* [网络处理器][TIMBotNetworkHandler]: 可通过 [Bot.network] 访问
* [机器人账号信息][BotAccount]: 可通过 [Bot.account] 访问
* <br></br>
*
* 若你需要得到机器人的 QQ 账号, 请访问 [Bot.account]
* 若你需要得到服务器上所有机器人列表, 请访问 [Bot.instances]
*
......@@ -28,7 +30,7 @@ import net.mamoe.mirai.utils.MiraiLogger
* a [ContactSystem], which manage contacts such as [QQ] and [Group];
* a [TIMBotNetworkHandler], which manages the connection to the server;
* a [BotAccount], which stores the account information(e.g. qq number the bot)
* <br></br>
*
* To of all the QQ contacts, access [Bot.account]
* To of all the Robot instance, access [Bot.instances]
*
......@@ -42,7 +44,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
val contacts = ContactSystem()
val network: BotNetworkHandler = TIMBotNetworkHandler(this)
val network: BotNetworkHandler<*> = TIMBotNetworkHandler(this)
init {
instances.add(this)
......@@ -59,25 +61,22 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
*/
inner class ContactSystem internal constructor() {
val groups = ContactList<Group>()
private val groupsLock = Mutex()
val qqs = ContactList<QQ>()
private val qqsLock = Mutex()
fun getQQ(qqNumber: Long): QQ {
synchronized(this.qqs) {
if (!this.qqs.containsKey(qqNumber)) {
this.qqs[qqNumber] = QQ(this@Bot, qqNumber)
}
return this.qqs[qqNumber]!!
}
}
/**
* 通过群号码获取群对象.
* 注意: 在并发调用时, 这个方法并不是原子的.
*/
fun getQQ(qqNumber: Long): QQ = qqs.getOrPut(qqNumber) { QQ(this@Bot, qqNumber) }
/**
* 通过群号码获取群对象.
* 注意: 在并发调用时, 这个方法并不是原子的.
*/
fun getGroupByNumber(groupNumber: Long): Group = groups.getOrPut(groupNumber) { Group(this@Bot, groupNumber) }
fun getGroupByNumber(groupNumber: Long): Group {
synchronized(this.groups) {
if (!this.groups.containsKey(groupNumber)) {
this.groups[groupNumber] = Group(this@Bot, groupNumber)
}
return this.groups[groupNumber]!!
}
}
fun getGroupById(groupId: Long): Group {
return getGroupByNumber(Group.groupIdToNumber(groupId))
......@@ -86,7 +85,6 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
fun close() {
this.network.close()
this.contacts.groups.values.forEach { it.close() }
this.contacts.groups.clear()
this.contacts.qqs.clear()
}
......@@ -94,8 +92,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
companion object {
val instances: MutableList<Bot> = mutableListOf()
private var id = 0
private val idLock = Any()
fun nextId(): Int = synchronized(idLock) { id++ }
private val id = atomic(0)
fun nextId(): Int = id.addAndGet(1)
}
}
\ No newline at end of file
@file:Suppress("unused")
@file:Suppress("unused", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai
import kotlinx.io.core.readBytes
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.goto
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.ContactList
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.toUHexString
import java.text.SimpleDateFormat
import java.util.*
/**
* The mirror of functions in inner classes of [Bot]
......@@ -23,7 +21,10 @@ import java.util.*
//Contacts
fun Bot.getQQ(number: Long): QQ = this.contacts.getQQ(number)
fun Bot.getQQ(number: UInt): QQ = getQQ(number.toLong())
fun Bot.getGroupByNumber(number: Long): Group = this.contacts.getGroupByNumber(number)
fun Bot.getGroupByNumber(number: UInt): Group = getGroupByNumber(number.toLong())
fun Bot.getGroupById(number: Long): Group = this.contacts.getGroupById(number)
......@@ -33,9 +34,11 @@ val Bot.qqs: ContactList<QQ> get() = this.contacts.qqs
//NetworkHandler
suspend fun Bot.sendPacket(packet: ClientPacket) = this.network.socket.sendPacket(packet)
suspend fun Bot.sendPacket(packet: ClientPacket) = this.network.sendPacket(packet)
suspend fun Bot.login(configuration: LoginConfiguration.() -> Unit): LoginResult = this.network.login(LoginConfiguration().apply(configuration))
suspend fun Bot.login(): LoginState = this.network.login()
suspend fun Bot.login(): LoginResult = this.network.login(LoginConfiguration.Default)
//BotAccount
val Bot.qqNumber: Long get() = this.account.qqNumber
......@@ -45,31 +48,19 @@ val Bot.qqNumber: Long get() = this.account.qqNumber
fun Bot.log(o: Any?) = info(o)
fun Bot.println(o: Any?) = info(o)
fun Bot.info(o: Any?) = print(this, o.toString(), LoggerTextFormat.RESET)
fun Bot.info(o: Any?) = this.logger.logInfo(o)
fun Bot.error(o: Any?) = print(this, o.toString(), LoggerTextFormat.RED)
fun Bot.error(o: Any?) = this.logger.logError(o)
fun Bot.notice(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_BLUE)
fun Bot.purple(o: Any?) = this.logger.logPurple(o)
fun Bot.purple(o: Any?) = print(this, o.toString(), LoggerTextFormat.PURPLE)
fun Bot.cyan(o: Any?) = this.logger.logCyan(o)
fun Bot.green(o: Any?) = this.logger.logGreen(o)
fun Bot.cyan(o: Any?) = print(this, o.toString(), LoggerTextFormat.LIGHT_CYAN)
fun Bot.green(o: Any?) = print(this, o.toString(), LoggerTextFormat.GREEN)
fun Bot.debug(o: Any?) = print(this, o.toString(), LoggerTextFormat.YELLOW)
fun Bot.debug(o: Any?) = this.logger.logDebug(o)
fun Bot.printPacketDebugging(packet: ServerPacket) {
debug("Packet=$packet")
debug("Packet size=" + packet.input.goto(0).readAllBytes().size)
debug("Packet data=" + packet.input.goto(0).readAllBytes().toUHexString())
}
private fun print(bot: Bot, value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
println("$color[Mirai] $s #R${bot.id}: $value")
}
private fun print(value: String?, color: LoggerTextFormat = LoggerTextFormat.WHITE) {
val s = SimpleDateFormat("MM-dd HH:mm:ss").format(Date())
println("$color[Mirai] $s : $value")
debug("Packet size=" + packet.input.readBytes().size)
debug("Packet data=" + packet.input.readBytes().toUHexString())
}
\ No newline at end of file
package net.mamoe.mirai
//expect fun s(): String
/**
* @author Him188moe
*/
object Mirai {
const val VERSION: String = "1.0.0"
}
\ No newline at end of file
......@@ -4,7 +4,7 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Group.Companion.groupNumberToId
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.utils.ContactList
import java.io.Closeable
import kotlin.jvm.JvmStatic
/**
* 群.
......@@ -21,22 +21,20 @@ import java.io.Closeable
* Java 调用 [groupNumberToId] : `Group.groupNumberToId(number)`
* @author Him188moe
*/
class Group(bot: Bot, number: Long) : Contact(bot, number), Closeable {
class Group(bot: Bot, number: Long) : Contact(bot, number) {
val groupId = groupNumberToId(number)
val members = ContactList<QQ>()//todo members
val members: ContactList<QQ>
//todo members
get() = throw UnsupportedOperationException("Not yet supported")
override suspend fun sendMessage(message: MessageChain) {
bot.network.message.sendGroupMessage(this, message)
bot.network.event.sendGroupMessage(this, message)
}
override suspend fun sendXMLMessage(message: String) {
}
override fun close() {
this.members.clear()
}
companion object {
@JvmStatic
fun groupNumberToId(number: Long): Long {//求你别出错
......
......@@ -12,34 +12,26 @@ import net.mamoe.mirai.message.MessageChain
* Java 获取 qq 号: `qq.getNumber()`
* Java 获取所属 bot: `qq.getBot()`
*
* A QQ instance helps you to receive message from or sendPacket message to.
* A QQ instance helps you to receive event from or sendPacket event to.
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
*
* @author Him188moe
*/
class QQ(bot: Bot, number: Long) : Contact(bot, number) {
override suspend fun sendMessage(message: MessageChain) {
bot.network.message.sendFriendMessage(this, message)
bot.network.event.sendFriendMessage(this, message)
}
override suspend fun sendXMLMessage(message: String) {
}
}
/**
/**
* At(@) this account.
*
* @return an instance of [Message].
*/
fun at(): At {
fun QQ.at(): At {
return At(this)
}
/*
Make that we can use (QQ + QQ2 + QQ3).sendMessage( )
operator fun plus(qq: QQ): QQCombination {
}*/
}
\ No newline at end of file
......@@ -2,10 +2,16 @@
package net.mamoe.mirai.event
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.event.internal.broadcastInternal
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.utils.log
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmOverloads
/**
* 所有事件的基类.
......@@ -13,7 +19,6 @@ import net.mamoe.mirai.event.internal.broadcastInternal
*
* @see [broadcast] 广播事件
* @see [subscribe] 监听事件
* @author Him188moe
*/
abstract class Event {
......@@ -31,7 +36,6 @@ abstract class Event {
*
* @throws UnsupportedOperationException 如果事件没有实现 [Cancellable]
*/
@Throws(UnsupportedOperationException::class)
fun cancel() {
cancelled = true
}
......@@ -39,8 +43,6 @@ abstract class Event {
/**
* 实现这个接口的事件可以被取消.
*
* @author Him188moe
*/
interface Cancellable {
val cancelled: Boolean
......@@ -51,8 +53,23 @@ interface Cancellable {
/**
* 广播一个事件的唯一途径
*/
@Synchronized
@Suppress("UNCHECKED_CAST")
suspend fun <E : Event> E.broadcast(): E = withContext(EventScope.coroutineContext) { this@broadcast.broadcastInternal() }
@JvmOverloads
suspend fun <E : Event> E.broadcast(context: CoroutineContext? = null): E {
var ctx = EventScope.coroutineContext
if (context != null) {
if (context[CoroutineExceptionHandler] == null)
ctx += CoroutineExceptionHandler { _, e -> e.log() }
ctx += context
}
return withContext(ctx) { this@broadcast.broadcastInternal() }
}
/**
* 事件协程作用域.
* 所有的事件 [broadcast] 过程均在此作用域下运行.
*
* 然而, 若在事件处理过程中使用到 [Contact.sendMessage] 等会 [发送数据包][BotNetworkHandler.sendPacket] 的方法,
* 发送过程将会通过 [withContext] 将协程切换到 [BotNetworkHandler.NetworkScope]
*/
object EventScope : CoroutineScope by CoroutineScope(Dispatchers.Default)//todo may change
\ No newline at end of file
......@@ -5,6 +5,7 @@ package net.mamoe.mirai.event
import net.mamoe.mirai.event.internal.Handler
import net.mamoe.mirai.event.internal.listeners
import net.mamoe.mirai.event.internal.subscribeInternal
import kotlin.jvm.Synchronized
import kotlin.reflect.KClass
enum class ListeningStatus {
......@@ -76,8 +77,8 @@ inline fun <reified E : Event> subscribeAll(noinline listeners: ListenerBuilder<
* }
*
* untilFalse {
* it.reply("你发送了 ${it.message}")
* it.message eq "停止"
* it.reply("你发送了 ${it.event}")
* it.event eq "停止"
* }
* }
* ```
......
......@@ -3,9 +3,7 @@ package net.mamoe.mirai.event.events
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.Event
/**
* @author Him188moe
*/
abstract class BotEvent(val bot: Bot) : Event()
class BotLoginSucceedEvent(bot: Bot) : BotEvent(bot)
\ No newline at end of file
......@@ -5,9 +5,7 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
*/
abstract class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot)
/**
......@@ -16,15 +14,11 @@ abstract class FriendEvent(bot: Bot, val sender: QQ) : BotEvent(bot)
* @author Him188moe
*/
class FriendMessageEvent(bot: Bot, sender: QQ, val message: MessageChain) : FriendEvent(bot, sender) {
@JvmSynthetic
suspend inline fun reply(message: Message) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: String) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: List<Message>) = sender.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: MessageChain) = sender.sendMessage(message)//shortcut
}
\ No newline at end of file
......@@ -6,24 +6,16 @@ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
/**
* @author Him188moe
*/
abstract class GroupEvent(bot: Bot, val group: Group) : BotEvent(bot)
/**
* @author Him188moe
*/
class GroupMessageEvent(bot: Bot, group: Group, val sender: QQ, val message: MessageChain) : GroupEvent(bot, group) {
@JvmSynthetic
suspend inline fun reply(message: Message) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: String) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: List<Message>) = group.sendMessage(message)
@JvmSynthetic
suspend inline fun reply(message: MessageChain) = group.sendMessage(message)
}
\ No newline at end of file
......@@ -8,9 +8,7 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
/* Abstract */
/**
* @author Him188moe
*/
sealed class PacketEvent<out P : Packet>(bot: Bot, open val packet: P) : BotEvent(bot)
......
package net.mamoe.mirai.event.internal
import kotlinx.coroutines.delay
import net.mamoe.mirai.Bot
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.dataInputStream
import net.mamoe.mirai.utils.BotAccount
import net.mamoe.mirai.utils.Console
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.isSuperclassOf
/**
* 监听和广播实现
......@@ -42,14 +33,16 @@ class Handler<E : Event>(val handler: suspend (E) -> ListeningStatus) : Listener
internal val <E : Event> KClass<E>.listeners: EventListeners<E> get() = EventListenerManger.get(this)
internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf()
internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf() {
val lock = Mutex()
}
internal object EventListenerManger {
private val registries: MutableMap<KClass<out Event>, EventListeners<out Event>> = mutableMapOf()
@Suppress("UNCHECKED_CAST")
internal fun <E : Event> get(clazz: KClass<E>): EventListeners<E> {
synchronized(clazz) {
//synchronized(clazz) {
if (registries.containsKey(clazz)) {
return registries[clazz] as EventListeners<E>
} else {
......@@ -58,13 +51,13 @@ internal object EventListenerManger {
return it
}
}
}
//}
}
}
@Suppress("UNCHECKED_CAST")
internal suspend fun <E : Event> E.broadcastInternal(): E {
suspend fun callListeners(listeners: EventListeners<in E>) {
suspend fun callListeners(listeners: EventListeners<in E>) = listeners.lock.withLock {
val iterator = listeners.iterator()
while (iterator.hasNext()) {
if (iterator.next().onEvent(this) == ListeningStatus.STOPPED) {
......@@ -74,24 +67,9 @@ internal suspend fun <E : Event> E.broadcastInternal(): E {
}
callListeners(this::class.listeners as EventListeners<in E>)
this::class.allSuperclasses.forEach {
//println("super: " + it.simpleName)
if (Event::class.isSuperclassOf(it)) {
callListeners((it as KClass<out Event>).listeners as EventListeners<in E>)
}
}
loopAllListeners(this::class) { callListeners(it as EventListeners<in E>) }
return this
}
suspend fun main() {
ServerPacketReceivedEvent::class.subscribeAlways {
println("got it")
}
println(ServerPacketReceivedEvent::class.listeners.size)
ServerPacketReceivedEvent(Bot(BotAccount(1, ""), Console()), object : ServerPacket(byteArrayOf().dataInputStream()) {}).broadcast()
delay(1000)
}
\ No newline at end of file
internal expect inline fun <E : Event> loopAllListeners(clazz: KClass<E>, consumer: (EventListeners<in E>) -> Unit)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.message
/**
* @author LamGC
*/
@Suppress("EnumEntryName", "unused", "SpellCheckingInspection")
enum class FaceID constructor(val id: UByte) {
unknown(0xffu),
// TODO: 2019/9/1 添加更多表情
jingya(0u),
piezui(1u),
se(2u),
fadai(3u),
deyi(4u),
liulei(5u),
haixiu(6u),
bizui(7u),
shui(8u),
daku(9u),
ganga(10u),
fanu(11u),
tiaopi(12u),
ciya(13u),
weixiao(14u),
nanguo(15u),
ku(16u),
zhuakuang(18u),
tu(19u),
touxiao(20u),
keai(21u),
baiyan(22u),
aoman(23u),
ji_e(24u),
kun(25u),
jingkong(26u),
liuhan(27u),
hanxiao(28u),
dabing(29u),
fendou(30u),
zhouma(31u),
yiwen(32u),
yun(34u),
zhemo(35u),
shuai(36u),
kulou(37u),
qiaoda(38u),
zaijian(39u),
fadou(41u),
aiqing(42u),
tiaotiao(43u),
zhutou(46u),
yongbao(49u),
dan_gao(53u),
shandian(54u),
zhadan(55u),
dao(56u),
zuqiu(57u),
bianbian(59u),
kafei(60u),
fan(61u),
meigui(63u),
diaoxie(64u),
aixin(66u),
xinsui(67u),
liwu(69u),
taiyang(74u),
yueliang(75u),
qiang(76u),
ruo(77u),
woshou(78u),
shengli(79u),
feiwen(85u),
naohuo(86u),
xigua(89u),
lenghan(96u),
cahan(97u),
koubi(98u),
guzhang(99u),
qiudale(100u),
huaixiao(101u),
zuohengheng(102u),
youhengheng(103u),
haqian(104u),
bishi(105u),
weiqu(106u),
kuaikule(107u),
yinxian(108u),
qinqin(109u),
xia(110u),
kelian(111u),
caidao(112u),
pijiu(113u),
lanqiu(114u),
pingpang(115u),
shiai(116u),
piaochong(117u),
baoquan(118u),
gouyin(119u),
quantou(120u),
chajin(121u),
aini(122u),
bu(123u),
hao(124u),
zhuanquan(125u),
ketou(126u),
huitou(127u),
tiaosheng(128u),
huishou(129u),
jidong(130u),
jiewu(131u),
xianwen(132u),
zuotaiji(133u),
youtaiji(134u),
shuangxi(136u),
bianpao(137u),
denglong(138u),
facai(139u),
K_ge(140u),
gouwu(141u),
youjian(142u),
shuai_qi(143u),
hecai(144u),
qidao(145u),
baojin(146u),
bangbangtang(147u),
he_nai(148u),
xiamian(149u),
xiangjiao(150u),
feiji(151u),
kaiche(152u),
gaotiezuochetou(153u),
chexiang(154u),
gaotieyouchetou(155u),
duoyun(156u),
xiayu(157u),
chaopiao(158u),
xiongmao(159u),
dengpao(160u),
fengche(161u),
naozhong(162u),
dasan(163u),
caiqiu(164u),
zuanjie(165u),
shafa(166u),
zhijin(167u),
yao(168u),
shouqiang(169u),
qingwa(170u);
override fun toString(): String {
return "$name($id)"
}
companion object {
fun ofId(id: UByte): FaceID {
for (value in values()) {
if (value.id == id) {
return value
}
}
return unknown
}
}
}
......@@ -13,12 +13,12 @@ import net.mamoe.mirai.contact.QQ
* 这与使用 [String] 的使用非常类似.
*
* 比较 [Message] 与 [String] (使用 infix [Message.eq]):
* `if(message eq "你好") qq.sendMessage(message)`
* `if(event eq "你好") qq.sendMessage(event)`
*
* 连接 [Message] 与 [Message], [String], (使用 operator [Message.plus]):
* ```
* message = PlainText("Hello ")
* qq.sendMessage(message + "world")
* event = PlainText("Hello ")
* qq.sendMessage(event + "world")
* ```
*
* 但注意: 不能 `String + Message`. 只能 `Message + String`
......
......@@ -2,11 +2,9 @@
package net.mamoe.mirai.message
/**
* @author Him188moe
*/
@Suppress("unused")
enum class MessageType(private val value: UByte) {
enum class MessageType(val value: UByte) {
PLAIN_TEXT(0x03u),
AT(0x06u),
FACE(0x02u),
......
......@@ -10,7 +10,3 @@ fun String.toMessage(): PlainText = PlainText(this)
* 用 `this` 构造 [MessageChain]
*/
fun Message.toChain(): MessageChain = if (this is MessageChain) this else MessageChain(this)
\ No newline at end of file
fun MessageChain.containsType(clazz: Class<out Message>): Boolean = list.any { clazz.isInstance(it) }
operator fun MessageChain.contains(sub: Class<out Message>): Boolean = containsType(sub)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.message.internal
import kotlinx.io.core.*
import net.mamoe.mirai.message.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.*
import java.io.DataInputStream
internal fun ByteArray.parseMessageFace(): Face = dataDecode(this) {
internal fun ByteArray.parseMessageFace(): Face = read {
//00 01 AF 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 F0
//00 01 0C 0B 00 08 00 01 00 04 52 CC F5 D0 FF 00 02 14 4D
it.skip(1)
discardExact(1)
val id1 = FaceID.ofId(it.readLVNumber().toInt())//可能这个是id, 也可能下面那个
it.skip(it.readByte().toLong())
it.readLVNumber()//某id?
return@dataDecode Face(id1)
val id1 = FaceID.ofId(readLVNumber().toInt().toUByte())//可能这个是id, 也可能下面那个
discardExact(readByte().toLong())
readLVNumber()//某id?
return@read Face(id1)
}
internal fun ByteArray.parsePlainText(): PlainText = dataDecode(this) {
it.skip(1)
PlainText(it.readLVString())
internal fun ByteArray.parsePlainText(): PlainText = read {
discardExact(1)
PlainText(readLVString())
}
internal fun ByteArray.parseMessageImage0x06(): Image = dataDecode(this) {
it.skip(1)
MiraiLogger.debug("好友的图片")
MiraiLogger.debug(this.toUHexString())
val filenameLength = it.readShort()
val suffix = it.readString(filenameLength).substringAfter(".")
it.skip(this.size - 37 - 1 - filenameLength - 2)
val imageId = String(it.readNBytes(36))
MiraiLogger.debug(imageId)
it.skip(1)//0x41
return@dataDecode Image("{$imageId}.$suffix")
internal fun ByteArray.parseMessageImage0x06(): Image = read {
discardExact(1)
MiraiLogger.logDebug("好友的图片")
MiraiLogger.logDebug(this@parseMessageImage0x06.toUHexString())
val filenameLength = readShort()
val suffix = readString(filenameLength).substringAfter(".")
discardExact(this@parseMessageImage0x06.size - 37 - 1 - filenameLength - 2)
val imageId = readString(36)
MiraiLogger.logDebug(imageId)
discardExact(1)//0x41
return@read Image("{$imageId}.$suffix")
}
internal fun ByteArray.parseMessageImage0x03(): Image = dataDecode(this) {
it.skip(1)
return@dataDecode Image(String(it.readLVByteArray()))
internal fun ByteArray.parseMessageImage0x03(): Image = read {
discardExact(1)
return@read Image(String(readLVByteArray()))
/*
println(String(it.readLVByteArray()))
it.readTLVMap()
return@dataDecode Image(String(it.readLVByteArray().cutTail(5).getRight(42)))
println(String(readLVByteArray()))
readTLVMap()
return Image(String(readLVByteArray().cutTail(5).getRight(42)))
/
it.skip(data.size - 47)
val imageId = String(it.readNBytes(42))
it.skip(1)//0x41
it.skip(1)//0x42
it.skip(1)//0x43
it.skip(1)//0x41
return@dataDecode Image(imageId)*/
discardExact(data.size - 47)
val imageId = String(readBytes(42))
discardExact(1)//0x41
discardExact(1)//0x42
discardExact(1)//0x43
discardExact(1)//0x41
return Image(imageId)*/
}
internal fun ByteArray.parseMessageChain(): MessageChain = dataDecode(this) {
it.readMessageChain()
internal fun ByteArray.parseMessageChain(): MessageChain = read {
readMessageChain()
}
internal fun DataInputStream.readMessage(): Message? {
internal fun ByteReadPacket.readMessage(): Message? {
val messageType = this.readByte().toInt()
val sectionLength = this.readShort().toLong()//sectionLength: short
val sectionData = this.readNBytes(sectionLength)
val sectionData = this.readBytes(sectionLength.toInt())//use buffer instead
return when (messageType) {
0x01 -> sectionData.parsePlainText()
0x02 -> sectionData.parseMessageFace()
......@@ -81,7 +82,7 @@ internal fun DataInputStream.readMessage(): Message? {
println(value.size)
println(value.toUHexString())
//todo 未知压缩算法
this.skip(7)//几个TLV
this.discardExact(7)//几个TLV
return PlainText(String(value))
}
......@@ -92,21 +93,21 @@ internal fun DataInputStream.readMessage(): Message? {
else -> {
println("未知的messageType=0x${messageType.toByte().toUHexString()}")
println("后文=${this.readAllBytes().toUHexString()}")
println("后文=${this.readBytes().toUHexString()}")
null
}
}
}
fun DataInputStream.readMessageChain(): MessageChain {
fun ByteReadPacket.readMessageChain(): MessageChain {
val chain = MessageChain()
var got: Message? = null
do {
if (got != null) {
chain.concat(got)
}
if (this.available() == 0) {
if (this.remaining == 0L) {
return chain
}
got = this.readMessage()
......@@ -114,34 +115,34 @@ fun DataInputStream.readMessageChain(): MessageChain {
return chain
}
fun MessageChain.toByteArray(): ByteArray = dataEncode { result ->
this@toByteArray.list.forEach { message ->
result.write(with(message) {
fun MessageChain.toPacket(): ByteReadPacket = buildPacket {
this@toPacket.list.forEach { message ->
writePacket(with(message) {
when (this) {
is Face -> dataEncode { section ->
section.writeByte(MessageType.FACE.intValue)
is Face -> buildPacket {
writeUByte(MessageType.FACE.value)
section.writeLVByteArray(dataEncode { child ->
child.writeShort(1)
child.writeByte(this.id.id)
writeLVPacket {
writeShort(1)
writeUByte(id.id)
child.writeHex("0B 00 08 00 01 00 04 52 CC F5 D0 FF")
writeHex("0B 00 08 00 01 00 04 52 CC F5 D0 FF")
child.writeShort(2)
child.writeByte(0x14)//??
child.writeByte(this.id.id + 65)
})
writeShort(2)
writeByte(0x14)//??
writeUByte((id.id + 65u).toUByte())
}
}
is At -> throw UnsupportedOperationException("At is not supported now but is expecting to be supported")
is Image -> dataEncode { section ->
section.writeByte(MessageType.IMAGE.intValue)
is Image -> buildPacket {
writeUByte(MessageType.IMAGE.value)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x02)
child.writeLVString(this.imageId)
child.writeHex("04 00 " +
writeLVPacket {
writeByte(0x02)
writeLVString(imageId)
writeHex("04 00 " +
"04 9B 53 B0 08 " +
"05 00 " +
"04 D9 8A 5A 70 " +
......@@ -149,19 +150,19 @@ fun MessageChain.toByteArray(): ByteArray = dataEncode { result ->
"04 00 00 00 50 " +
"07 00 " +
"01 43 08 00 00 09 00 01 01 0B 00 00 14 00 04 11 00 00 00 15 00 04 00 00 02 BC 16 00 04 00 00 02 BC 18 00 04 00 00 7D 5E FF 00 5C 15 36 20 39 32 6B 41 31 43 39 62 35 33 62 30 30 38 64 39 38 61 35 61 37 30 20")
child.writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
child.writeBytes(this.imageId)
child.writeByte(0x41)
})
writeHex("20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20")
writeStringUtf8(imageId)
writeByte(0x41)
}
}
is PlainText -> dataEncode { section ->
section.writeByte(MessageType.PLAIN_TEXT.intValue)
is PlainText -> buildPacket {
writeUByte(MessageType.PLAIN_TEXT.value)
section.writeLVByteArray(dataEncode { child ->
child.writeByte(0x01)
child.writeLVString(this.stringValue)
})
writeLVPacket {
writeByte(0x01)
writeLVString(stringValue)
}
}
else -> throw UnsupportedOperationException("${this::class.simpleName} is not supported")
......
package net.mamoe.mirai.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.io.core.Closeable
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocket
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket
import net.mamoe.mirai.network.protocol.tim.handler.MessagePacketHandler
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.Packet
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
import net.mamoe.mirai.network.protocol.tim.handler.*
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
import net.mamoe.mirai.utils.LoginConfiguration
import net.mamoe.mirai.utils.MiraiDatagramChannel
/**
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
......@@ -20,42 +20,49 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginState
* - [BotSocket]: 处理数据包底层的发送([ByteArray])
* - [PacketHandler]: 制作 [ClientPacket] 并传递给 [BotSocket] 发送; 分析 [ServerPacket] 并处理
*
* 其中, [PacketHandler] 由 4 个子模块构成:
* - [DebugPacketHandler] 输出 [Packet.toString]
* - [LoginHandler] 处理 touch/login/verification code 相关
* - [MessagePacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* 其中, [PacketHandler] 由 3 个子模块构成:
* - [LoginHandler] 处理 sendTouch/login/verification code 相关
* - [EventPacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等)
*
* A BotNetworkHandler is used to connect with Tencent servers.
*
* @author Him188moe
*/
interface BotNetworkHandler {
interface BotNetworkHandler<Socket : DataPacketSocket> : Closeable {
/**
* 网络层处理器. 用于编码/解码 [Packet], 发送/接受 [ByteArray]
* [BotNetworkHandler] 的协程作用域.
* 所有 [BotNetworkHandler] 的协程均启动在此作用域下.
*
* [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [MiraiDatagramChannel.read]
* - 心跳 Job [ClientHeartbeatPacket]
* - SKey 刷新 [ClientSKeyRefreshmentRequestPacket]
* - 所有数据包处理和发送
*
* java 调用方式: `botNetWorkHandler.getSocket()`
* [BotNetworkHandler.close] 时将会 [取消][CoroutineScope.cancel] 所有此作用域下的协程
*/
val socket: DataPacketSocket
val NetworkScope: CoroutineScope
var socket: Socket
/**
* 消息处理. 如发送好友消息, 接受群消息并触发事件
*
* java 调用方式: `botNetWorkHandler.getMessage()`
* 事件处理. 如发送好友消息, 接受群消息并触发事件
*/
val message: MessagePacketHandler
val event: EventPacketHandler
/**
* 动作处理. 如发送好友请求, 处理别人发来的好友请求等
*
* java 调用方式: `botNetWorkHandler.getAction()`
*/
val action: ActionPacketHandler
/**
* 尝试登录
* [PacketHandler] 列表
*/
suspend fun login(): LoginState
val packetHandlers: PacketHandlerList
/**
* 尝试登录. 将会依次尝试登录到可用的服务器. 在任一服务器登录完成后返回登录结果
*/
suspend fun login(configuration: LoginConfiguration): LoginResult
/**
* 添加一个临时包处理器
......@@ -64,5 +71,12 @@ interface BotNetworkHandler {
*/
suspend fun addHandler(temporaryPacketHandler: TemporaryPacketHandler<*>)
fun close()
/**
* 发送数据包
*/
suspend fun sendPacket(packet: ClientPacket)
override fun close() {
NetworkScope.cancel("handler closed", HandlerClosedException())
}
}
\ No newline at end of file
package net.mamoe.mirai.network
class HandlerClosedException : Exception()
\ No newline at end of file
package net.mamoe.mirai.network
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocket
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.getGTK
import kotlin.jvm.JvmSynthetic
/**
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
......@@ -16,8 +18,9 @@ import net.mamoe.mirai.utils.getGTK
*/
class LoginSession(
val bot: Bot,
val sessionKey: ByteArray,
val socket: DataPacketSocket
val sessionKey: ByteArray,//TODO 协议抽象? 可能并不是所有协议均需要 sessionKey
val socket: DataPacketSocket,
val scope: CoroutineScope
) {
/**
......@@ -28,6 +31,7 @@ class LoginSession(
/**
* Web api 使用
*/
@ExperimentalStdlibApi
var sKey: String = ""
set(value) {
field = value
......@@ -39,6 +43,7 @@ class LoginSession(
*/
var gtk: Int = 0
val isOpen: Boolean get() = socket.isOpen
/**
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket].
......@@ -56,7 +61,7 @@ class LoginSession(
* @param P 期待的包
* @param handlerTemporary 处理器.
*/
@JvmSynthetic
//@JvmSynthetic
suspend inline fun <reified P : ServerPacket> expectPacket(handlerTemporary: TemporaryPacketHandler<P>.() -> Unit): CompletableDeferred<Unit> {
val deferred = CompletableDeferred<Unit>()
this.bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this).also(handlerTemporary))
......@@ -89,3 +94,6 @@ class LoginSession(
return deferred
}
}
suspend fun LoginSession.distributePacket(packet: ServerPacket) = this.socket.distributePacket(packet)
\ No newline at end of file
......@@ -2,17 +2,9 @@
package net.mamoe.mirai.network.protocol.tim
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import java.net.InetAddress
import java.util.*
import java.util.stream.Collectors
import net.mamoe.mirai.utils.solveIpAddress
/**
* @author Him188moe
*/
object TIMProtocol {
val SERVER_IP: List<String> = {
//add("183.60.56.29")
......@@ -25,7 +17,7 @@ object TIMProtocol {
"sz8.tencent.com",
"sz9.tencent.com",
"sz2.tencent.com"
).forEach { list.add(InetAddress.getByName(it).hostAddress) }
).forEach { list.add(solveIpAddress(it)) }
list.toList()
}()
......@@ -45,7 +37,7 @@ object TIMProtocol {
const val constantData2 = "00 00 04 53 00 00 00 01 00 00 15 85 "
/**
* Touch 发出时写入, 并用于加密, 接受 touch response 时解密.
* Touch 发出时写入, 并用于加密, 接受 sendTouch response 时解密.
*/
const val touchKey = "A4 F1 91 88 C9 82 14 99 0C 9E 56 55 91 23 C8 3D"//16
......@@ -66,11 +58,6 @@ object TIMProtocol {
*/
const val key0836 = "EF 4A 36 6A 16 A8 E6 3D 2E EA BD 1F 98 C1 3C DA"//16
@JvmStatic
fun main(args: Array<String>) {
println(TEA.decrypt(publicKey.hexToBytes(), key0836).toUHexString())
}
/**
* 并非常量. 是 publicKey 与 key0836 的算法计算结果
*/
......@@ -100,27 +87,4 @@ object TIMProtocol {
*/
const val messageConst1 = "00 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91"
// TIM最新 22 00 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
fun hexToBytes(hex: String): ByteArray {
hex.hashCode().let { id ->
if (hexToByteArrayCacheMap.containsKey(id)) {
return hexToByteArrayCacheMap[id]!!.clone()
} else {
hexToUBytes(hex).toByteArray().let {
hexToByteArrayCacheMap[id] = it.clone()
return it
}
}
}
}
fun hexToUBytes(hex: String): UByteArray = Arrays
.stream(hex.split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray())
.map { value -> value.trim { it <= ' ' } }
.map { s -> s.toUByte(16) }
.collect(Collectors.toList()).toUByteArray()
}
package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientAccountInfoRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerAccountInfoResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.AddFriendResult
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientCanAddFriendPacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRefreshmentRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ClientSKeyRequestPacket
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerSKeyResponsePacket
import net.mamoe.mirai.task.MiraiThreadPool
import net.mamoe.mirai.utils.getGTK
import java.awt.image.BufferedImage
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.function.Supplier
import net.mamoe.mirai.utils.hexToBytes
/**
* 动作: 获取好友列表, 点赞, 踢人等.
......@@ -32,12 +23,13 @@ import java.util.function.Supplier
* @author Him188moe
*/
class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private val addFriendSessions = Collections.synchronizedCollection(mutableListOf<AddFriendSession>())
private val uploadImageSessions = Collections.synchronizedCollection(mutableListOf<UploadImageSession>())
private val addFriendSessions = mutableListOf<AddFriendSession>()
private val uploadImageSessions = mutableListOf<UploadImageSession>()
private var sKeyRefresherFuture: ScheduledFuture<*>? = null
private var sKeyRefresherJob: Job? = null
@ExperimentalStdlibApi
override suspend fun onPacketReceived(packet: ServerPacket) {
when (packet) {
is ServerCanAddFriendResponsePacket -> {
......@@ -65,11 +57,13 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
session.sKey = packet.sKey
session.cookies = "uin=o" + session.bot.account.qqNumber + ";skey=" + session.sKey + ";"
sKeyRefresherFuture = MiraiThreadPool.instance.scheduleWithFixedDelay({
runBlocking {
sKeyRefresherJob = session.scope.launch {
while (session.isOpen) {
delay(1800000)
session.socket.sendPacket(ClientSKeyRefreshmentRequestPacket(session.bot.account.qqNumber, session.sessionKey))
}
}, 1800000, 1800000, TimeUnit.MILLISECONDS)
}
session.gtk = getGTK(session.sKey)
}
......@@ -82,15 +76,9 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
}
}
suspend fun addFriend(qqNumber: Long, message: Supplier<String>) {
addFriend(qqNumber, lazy { message.get() })
}
@JvmSynthetic
suspend fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableFuture<AddFriendResult> {
val future = CompletableFuture<AddFriendResult>()
//@JvmSynthetic
suspend fun addFriend(qqNumber: Long, message: Lazy<String> = lazyOf("")): CompletableDeferred<AddFriendResult> {
val future = CompletableDeferred<AddFriendResult>()
val session = AddFriendSession(qqNumber, future, message)
// uploadImageSessions.add(session)
session.sendAddRequest()
......@@ -108,14 +96,14 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
}
override fun close() {
this.sKeyRefresherFuture?.cancel(true)
this.sKeyRefresherFuture = null
this.sKeyRefresherJob?.cancel()
this.sKeyRefresherJob = null
}
private inner class UploadImageSession(
private val group: Long,
private val future: CompletableFuture<AddFriendResult>,
private val image: BufferedImage
private val future: CompletableDeferred<AddFriendResult>
//private val image: BufferedImage
) {
lateinit var id: ByteArray
......@@ -167,7 +155,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
private inner class AddFriendSession(
private val qq: Long,
private val future: CompletableFuture<AddFriendResult>,
private val future: CompletableDeferred<AddFriendResult>,
private val message: Lazy<String>
) {
lateinit var id: ByteArray
......@@ -180,7 +168,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
when (packet) {
is ServerCanAddFriendResponsePacket -> {
if (!(packet.idByteArray[2] == id[0] && packet.idByteArray[3] == id[1])) {
if (!(packet.idByteArray.contentEquals(id))) {
return
}
......@@ -211,7 +199,7 @@ class ActionPacketHandler(session: LoginSession) : PacketHandler(session) {
suspend fun sendAddRequest() {
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.packetIdLast })
session.socket.sendPacket(ClientCanAddFriendPacket(session.bot.account.qqNumber, qq, session.sessionKey).also { this.id = it.idHex.hexToBytes() })
}
fun close() {
......
package net.mamoe.mirai.network.protocol.tim.handler
import kotlinx.io.core.Closeable
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.ServerPacketReceivedEvent
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.MiraiDatagramChannel
/**
* 网络接口.
......@@ -14,9 +16,21 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
*
* @author Him188moe
*/
interface DataPacketSocket {
interface DataPacketSocket : Closeable {
val owner: Bot
val serverIp: String
/**
* UDP 通道
*/
val channel: MiraiDatagramChannel
/**
* 是否开启
*/
val isOpen: Boolean
/**
* 分发数据包给 [PacketHandler]
*/
......@@ -31,7 +45,5 @@ interface DataPacketSocket {
*/
suspend fun sendPacket(packet: ClientPacket)
fun isClosed(): Boolean
fun close()
override fun close()
}
\ No newline at end of file
......@@ -9,14 +9,13 @@ import net.mamoe.mirai.getGroupByNumber
import net.mamoe.mirai.getQQ
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerFriendMessageEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupMessageEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerGroupUploadFileEventPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.distributePacket
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientSendFriendMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ClientSendGroupMessagePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.utils.MiraiLogger
/**
* 处理消息事件, 承担消息发送任务.
......@@ -24,10 +23,10 @@ import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessage
* @author Him188moe
*/
@Suppress("EXPERIMENTAL_API_USAGE")
class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
class EventPacketHandler(session: LoginSession) : PacketHandler(session) {
internal var ignoreMessage: Boolean = true
override suspend fun onPacketReceived(packet: ServerPacket) {
override suspend fun onPacketReceived(packet: ServerPacket): Unit = with(session) {
when (packet) {
is ServerGroupUploadFileEventPacket -> {
//todo
......@@ -36,21 +35,28 @@ class MessagePacketHandler(session: LoginSession) : PacketHandler(session) {
is ServerFriendMessageEventPacket -> {
if (ignoreMessage) return
FriendMessageEvent(session.bot, session.bot.getQQ(packet.qq), packet.message).broadcast()
FriendMessageEvent(bot, bot.getQQ(packet.qq), packet.message).broadcast()
}
is ServerGroupMessageEventPacket -> {
if (ignoreMessage) return
if (packet.qq == session.bot.account.qqNumber) return
if (packet.qq.toLong() == bot.account.qqNumber) return
GroupMessageEvent(session.bot, session.bot.getGroupByNumber(packet.groupNumber), session.bot.getQQ(packet.qq), packet.message).broadcast()
GroupMessageEvent(bot, bot.getGroupByNumber(packet.groupNumber), bot.getQQ(packet.qq), packet.message).broadcast()
}
is ServerSendFriendMessageResponsePacket,
is ServerSendGroupMessageResponsePacket -> {
//ignored
}
is ServerFieldOnlineStatusChangedPacket.Encrypted -> distributePacket(packet.decrypt(sessionKey))
is ServerFieldOnlineStatusChangedPacket -> {
MiraiLogger.logInfo("${packet.qq.toLong()} 登录状态改变为 ${packet.status}")
//TODO
}
else -> {
//ignored
}
......
......@@ -2,6 +2,7 @@ package net.mamoe.mirai.network.protocol.tim.handler
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import kotlin.reflect.KClass
/**
* 数据包(接受/发送)处理器
......@@ -17,17 +18,18 @@ abstract class PacketHandler(
}
class PacketHandlerNode<T : PacketHandler>(
val clazz: Class<T>,
val clazz: KClass<T>,
val instance: T
)
fun PacketHandler.asNode(): PacketHandlerNode<PacketHandler> {
return PacketHandlerNode(this.javaClass, this)
fun <T : PacketHandler> T.asNode(): PacketHandlerNode<T> {
@Suppress("UNCHECKED_CAST")
return PacketHandlerNode(this::class as KClass<T>, this)
}
class PacketHandlerList : MutableList<PacketHandlerNode<*>> by mutableListOf() {
fun <T : PacketHandler> get(clazz: Class<T>): T {
operator fun <T : PacketHandler> get(clazz: KClass<T>): T {
this.forEach {
if (it.clazz == clazz) {
@Suppress("UNCHECKED_CAST")
......
......@@ -4,7 +4,6 @@ import kotlinx.coroutines.CompletableDeferred
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.util.concurrent.CompletableFuture
import kotlin.reflect.KClass
/**
......@@ -20,7 +19,7 @@ import kotlin.reflect.KClass
*
* @see LoginSession.expectPacket
*/
open class TemporaryPacketHandler<P : ServerPacket>(
class TemporaryPacketHandler<P : ServerPacket>(
private val expectationClass: KClass<P>,
private val deferred: CompletableDeferred<Unit>,
private val fromSession: LoginSession
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.utils.writeHex
//TODO 将序列 ID 从包 ID 中独立出来
abstract class ClientPacket : Packet(), Closeable {
/**
* Encode this packet.
*
* Before sending the packet, a [tail][TIMProtocol.tail] is added.
*/
protected abstract fun encode(builder: BytePacketBuilder)
companion object {
@Suppress("PrivatePropertyName")
private val UninitializedByteReadPacket = ByteReadPacket(IoBuffer.Empty, IoBuffer.EmptyPool)
}
/**
* 务必 [ByteReadPacket.close] 或 [close] 或使用 [Closeable.use]
*/
var packet: ByteReadPacket = UninitializedByteReadPacket
get() {
if (field === UninitializedByteReadPacket) build()
return field
}
private fun build(): ByteReadPacket {
packet = buildPacket {
writeHex(TIMProtocol.head)
writeHex(TIMProtocol.ver)
writeHex(idHex)
encode(this)
writeHex(TIMProtocol.tail)
}
return packet
}
override fun toString(): String = packetToString()
override fun close() = if (this.packet === UninitializedByteReadPacket) Unit else this.packet.close()
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readUByte
import kotlinx.io.core.readUInt
import net.mamoe.mirai.utils.OnlineStatus
import kotlin.properties.Delegates
/**
* 好友在线状态改变
*/
@PacketId("00 81")
class ServerFieldOnlineStatusChangedPacket(input: ByteReadPacket) : ServerPacket(input) {
var qq: UInt by Delegates.notNull()
lateinit var status: OnlineStatus
override fun decode() = with(input) {
qq = readUInt()
discardExact(8)
status = OnlineStatus.ofId(readUByte())
}
//在线 XX XX XX XX 01 00 00 00 00 00 00 00 0A 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00
//忙碌 XX XX XX XX 01 00 00 00 00 00 00 00 32 15 E3 10 00 01 2E 01 00 00 00 00 00 00 00 00 00 00 00 13 08 02 C2 76 E4 B8 DD 00 00 00 00 00 00 00 00 00 00 00
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerFieldOnlineStatusChangedPacket = ServerFieldOnlineStatusChangedPacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import java.io.DataInputStream
import net.mamoe.mirai.utils.encryptAndWrite
import net.mamoe.mirai.utils.writeHex
import net.mamoe.mirai.utils.writeQQ
import net.mamoe.mirai.utils.writeRandom
/**
* 获取升级天数等.
......@@ -14,13 +22,13 @@ class ClientAccountInfoRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
writeByte(0x88)
writeUByte(0x88.toUByte())
writeQQ(qq)
writeByte(0x00)
}
......@@ -28,7 +36,7 @@ class ClientAccountInfoRequestPacket(
}
@PacketId("00 5C")
class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(input) {
class ServerAccountInfoResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
//等级
//升级剩余活跃天数
//ignored
......@@ -37,7 +45,7 @@ class ServerAccountInfoResponsePacket(input: DataInputStream) : ServerPacket(inp
}
@PacketId("00 5C")
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerAccountInfoResponsePacket = ServerAccountInfoResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import java.io.DataInputStream
import java.io.IOException
import net.mamoe.mirai.utils.*
/**
* @author Him188moe
*/
@PacketId("00 58")
class ClientHeartbeatPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
@Throws(IOException::class)
override fun encode() {
this.writeRandom(2)
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.encryptAndWrite(sessionKey) {
......@@ -24,4 +24,4 @@ class ClientHeartbeatPacket(
}
}
class ServerHeartbeatResponsePacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerHeartbeatResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.utils.hexToUBytes
abstract class Packet {
open val idHex: String by lazy {
this::class.annotations.filterIsInstance<PacketId>().firstOrNull()?.value?.trim() ?: ""
}
open val fixedId: String by lazy {
when (this.idHex.length) {
0 -> "__ __ __ __"
2 -> this.idHex + " __ __ __"
5 -> this.idHex + " __ __"
7 -> this.idHex + " __"
else -> this.idHex
}
}
open val idByteArray: ByteArray by lazy {
idHex.hexToUBytes().toByteArray()
}
}
internal expect fun Packet.packetToString(): String
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
/**
* @author Him188moe
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FILE)
annotation class PacketId(
......
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.*
abstract class ServerPacket(val input: ByteReadPacket) : Packet(), Closeable {
override var idHex: String = EMPTY_ID_HEX
get() {
if (field === EMPTY_ID_HEX) {
idHex = (this::class.annotations.firstOrNull { it::class == PacketId::class } as? PacketId)?.value?.trim()
?: ""
}
return field
}
var encoded: Boolean = false
open fun decode() {
}
override fun close() = this.input.close()
companion object {
private const val EMPTY_ID_HEX = "EMPTY_ID_HEX"
}
override fun toString(): String = this.packetToString()
fun getFixedId(id: String): String = when (id.length) {
0 -> "__ __ __ __"
2 -> "$id __ __ __"
5 -> "$id __ __"
7 -> "$id __"
else -> id
}
fun decryptBy(key: ByteArray): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun decryptBy(key: IoBuffer): ByteReadPacket {
return ByteReadPacket(decryptAsByteArray(key))
}
fun decryptBy(keyHex: String): ByteReadPacket {
return this.decryptBy(keyHex.hexToBytes())
}
fun decryptBy(key1: ByteArray, key2: ByteArray): ByteReadPacket {
return TEA.decrypt(this.decryptAsByteArray(key1), key2).toReadPacket()
}
fun decryptBy(key1: String, key2: ByteArray): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2)
}
fun decryptBy(key1: String, key2: IoBuffer): ByteReadPacket {
return this.decryptBy(key1.hexToBytes(), key2.readBytes())
}
fun decryptBy(key1: ByteArray, key2: String): ByteReadPacket {
return this.decryptBy(key1, key2.hexToBytes())
}
fun decryptBy(keyHex1: String, keyHex2: String): ByteReadPacket {
return this.decryptBy(keyHex1.hexToBytes(), keyHex2.hexToBytes())
}
fun decryptAsByteArray(key: ByteArray): ByteArray {
return TEA.decrypt(input.readRemainingBytes().cutTail(1), key)
}
fun decryptAsByteArray(keyHex: String): ByteArray = this.decryptAsByteArray(keyHex.hexToBytes())
fun decryptAsByteArray(buffer: IoBuffer): ByteArray = this.decryptAsByteArray(buffer.readBytes())
}
fun <P : ServerPacket> P.setId(idHex: String): P {
this.idHex = idHex
return this
}
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toUHexString
class UnknownServerPacket(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() {
val raw = this.input.readBytes()
MiraiLogger.logDebug("UnknownServerPacket data: " + raw.toUHexString())
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): UnknownServerPacket = UnknownServerPacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
override fun toString(): String {
return LoggerTextFormat.LIGHT_RED.toString() + super.toString()
}
}
/*
ID: 00 17
长度 95
76 E4 B8 DD //1994701021
76 E4 B8 DD //1994701021
00 0B B9 A9 09 90 BB 54 1F //类似Event的uniqueId?
40 02
10 00 00 00
18 00
08 00
02 00
01 00
09 00
06 41 4B DA 4C 00 00
00 0A
00 04
01 00 00 00 00 00
00 06 00 00
00 0E
08 02
1A 02 08 49 0A 0C 08 A2 FF 8C F0
03 10 CA EB 8B ED 05
或者
长度63
00 00 27 10 76 E4 B8 DD
00 09 ED 26 64 73 0E CA 1F 40
00 12 00 00
00 08
00 0A
00 04
01 00 00
00 02
值都是一样的.
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.utils.gotoWhere
import net.mamoe.mirai.utils.toReadPacket
expect class PlatformImage
expect class ClientTryGetImageIDPacket(
botNumber: Long,
sessionKey: ByteArray,
groupNumberOrQQNumber: Long,
image: PlatformImage
) : ClientPacket
abstract class ServerTryGetImageIDResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): ServerTryGetImageIDResponsePacket {
val data = this.decryptAsByteArray(sessionKey)
println(data.size)
println(data.size)
if (data.size == 209) {
return ServerTryGetImageIDSuccessPacket(data.toReadPacket()).setId(this.idHex)
}
return ServerTryGetImageIDFailedPacket(data.toReadPacket())
}
}
}
/**
* 服务器未存有图片, 返回一个 key 用于客户端上传
*/
class ServerTryGetImageIDSuccessPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
lateinit var uKey: ByteArray
override fun decode() {
this.input.gotoWhere(ubyteArrayOf(0x42u, 0x80u, 0x01u))
uKey = this.input.readBytes(128)
}
}
/**
* 服务器已经存有这个图片
*/
class ServerTryGetImageIDFailedPacket(input: ByteReadPacket) : ServerTryGetImageIDResponsePacket(input) {
override fun decode() {
}
}
\ No newline at end of file
......@@ -2,12 +2,15 @@
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.getRandomByteArray
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
import java.util.*
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* 向服务器检查是否可添加某人为好友
......@@ -20,15 +23,11 @@ class ClientCanAddFriendPacket(
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
val packetIdLast = getRandomByteArray(2)
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode() {
this.write(packetIdLast)//id, 2bytes
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
......@@ -38,7 +37,7 @@ class ClientCanAddFriendPacket(
}
@PacketId("00 A7")
class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(input) {
class ServerCanAddFriendResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var state: State
enum class State {
......@@ -50,7 +49,8 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
override fun decode() {
val data = input.goto(0).readAllBytes()
input.readBytes()
val data = input.readRemainingBytes()
if (data.size == 99) {
state = State.ALREADY_ADDED
return
......@@ -61,13 +61,13 @@ class ServerCanAddFriendResponsePacket(input: DataInputStream) : ServerPacket(in
0x99u -> State.ALREADY_ADDED
0x03u,
0x04u -> State.FAILED
else -> throw IllegalArgumentException(Arrays.toString(data))
else -> throw IllegalArgumentException(data.contentToString())
}
}
@PacketId("00 A7")
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerCanAddFriendResponsePacket {
return ServerCanAddFriendResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
......@@ -84,15 +84,11 @@ class ClientAddFriendPacket(
val qq: Long,
val sessionKey: ByteArray
) : ClientPacket() {
val packetIdLast = getRandomByteArray(2)
override fun getFixedId(): String {
return idHex + " " + packetIdLast.toUHexString()
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode() {
this.write(packetIdLast)//id, 2bytes
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(bot)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
......@@ -104,17 +100,17 @@ class ClientAddFriendPacket(
}
class ServerAddFriendResponsePacket(input: DataInputStream) : ServerAddContactResponsePacket(input)
class ServerAddFriendResponsePacket(input: ByteReadPacket) : ServerAddContactResponsePacket(input)
class ServerAddGroupResponsePacket(input: DataInputStream) : ServerAddContactResponsePacket(input)
class ServerAddGroupResponsePacket(input: ByteReadPacket) : ServerAddContactResponsePacket(input)
/**
* 添加好友/群的回复
*/
abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPacket(input) {
abstract class ServerAddContactResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
class Raw(input: DataInputStream) : ServerPacket(input) {
class Raw(input: ByteReadPacket) : ServerPacket(input) {
override fun decode() {
......@@ -125,7 +121,7 @@ abstract class ServerAddContactResponsePacket(input: DataInputStream) : ServerPa
TODO()
}
class Encrypted(input: DataInputStream) : ServerPacket(input) {
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(sessionKey: ByteArray): Raw = Raw(this.decryptBy(sessionKey))
}
}
......
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.*
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toByteArray
import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
import java.io.DataInputStream
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.*
/**
* @author Him188moe
*/
@PacketId("00 CD")
class ClientSendFriendMessagePacket(
private val botQQ: Long,
......@@ -17,8 +17,8 @@ class ClientSendFriendMessagePacket(
private val sessionKey: ByteArray,
private val message: MessageChain
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(botQQ)
this.writeHex(TIMProtocol.fixVer2)
......@@ -30,7 +30,7 @@ class ClientSendFriendMessagePacket(
writeHex("37 0F")//TIM最新: 38 03
writeQQ(botQQ)
writeQQ(targetQQ)
write(md5(dataEncode { md5Key -> md5Key.writeQQ(targetQQ); md5Key.write(sessionKey) }))
writeFully(md5(buildPacket { writeQQ(targetQQ); writeFully(sessionKey) }.readBytes()))
writeHex("00 0B")
writeRandom(2)
writeTime()
......@@ -54,11 +54,11 @@ class ClientSendFriendMessagePacket(
writeHex(TIMProtocol.messageConst1)//... 85 E9 BB 91
writeZero(2)
write(message.toByteArray())
writePacket(message.toPacket())
/*
//Plain text
val bytes = message.toByteArray()
val bytes = event.toPacket()
it.writeByte(0x01)
it.writeShort(bytes.size + 3)
it.writeByte(0x01)
......@@ -69,4 +69,4 @@ class ClientSendFriendMessagePacket(
}
@PacketId("00 CD")
class ServerSendFriendMessageResponsePacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerSendFriendMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.action
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.message.internal.toByteArray
import net.mamoe.mirai.message.internal.toPacket
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
/**
* @author Him188moe
*/
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.*
@PacketId("00 02")
class ClientSendGroupMessagePacket(
private val botQQ: Long,
......@@ -18,39 +18,35 @@ class ClientSendGroupMessagePacket(
private val sessionKey: ByteArray,
private val message: MessageChain
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeRandom(2)
this.writeQQ(botQQ)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
val bytes = message.toByteArray()
writeByte(0x2A)
writeGroup(groupId)
writeLVByteArray(dataEncode { child ->
child.writeHex("00 01 01")
child.writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
writeLVPacket {
writeHex("00 01 01")
writeHex("00 00 00 00 00 00 00 4D 53 47 00 00 00 00 00")
child.writeTime()
child.writeRandom(4)
child.writeHex("00 00 00 00 09 00 86")
child.writeHex(TIMProtocol.messageConst1)
child.writeZero(2)
writeTime()
writeRandom(4)
writeHex("00 00 00 00 09 00 86")
writeHex(TIMProtocol.messageConst1)
writeZero(2)
//messages
child.write(bytes)
})
writePacket(message.toPacket())
}
/*it.writeByte(0x01)
it.writeShort(bytes.size + 3)
it.writeByte(0x01)
it.writeShort(bytes.size)
it.write(bytes)*/
println(toByteArray().toUHexString())
}
}
}
@PacketId("00 02")
class ServerSendGroupMessageResponsePacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerSendGroupMessageResponsePacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
......@@ -2,32 +2,32 @@
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.writeUByte
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.ClientLoginStatus
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.*
/**
* 改变在线状态: "我在线上", "隐身" 等
*
* @author Him188moe
*/
@PacketId("00 EC")
class ClientChangeOnlineStatusPacket(
private val qq: Long,
private val sessionKey: ByteArray,
private val loginStatus: ClientLoginStatus
private val loginStatus: OnlineStatus
) : ClientPacket() {
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode() {
this.writeRandom(2)//part of packet id
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
writeHex("01 00")
writeByte(loginStatus.id.toInt())
writeUByte(loginStatus.id)
writeHex("00 01 00 01 00 04 00 00 00 00")
}
}
......
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.TEA
import net.mamoe.mirai.utils.Tested
import net.mamoe.mirai.utils.hexToBytes
import java.io.DataOutputStream
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.utils.*
/**
* Password submission (0836_622)
*
* @author Him188moe
*/
@PacketId("08 36 31 03")
@Tested
......@@ -21,10 +20,11 @@ class ClientPasswordSubmissionPacket(
private val loginTime: Int,
private val loginIP: String,
private val privateKey: ByteArray,//16 random by client
private val token0825: ByteArray//56 from server
private val token0825: ByteArray,//56 from server
private val randomDeviceName: Boolean
) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.passwordSubmissionTLV1)
......@@ -36,7 +36,7 @@ class ClientPasswordSubmissionPacket(
//TODO shareKey 极大可能为 publicKey, key0836 计算得到
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
writePart1(qq, password, loginTime, loginIP, privateKey, token0825)
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, randomDeviceName)
writePart2()
}
}
......@@ -46,16 +46,13 @@ class ClientPasswordSubmissionPacket(
//但为简化处理, 特固定这个 id
@PacketId("08 36 31 04")
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
class ClientLoginResendPacket3104(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
@PacketId("08 36 31 05")
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, null)
class ClientLoginResendPacket3105(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
@PacketId("08 36 31 06")
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, tlv0006: ByteArray? = null)
: ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, tlv0006)
class ClientLoginResendPacket3106(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, token00BA: ByteArray, randomDeviceName: Boolean, tlv0006: IoBuffer? = null) : ClientLoginResendPacket(qq, password, loginTime, loginIP, privateKey, token0825, token00BA, randomDeviceName, tlv0006)
open class ClientLoginResendPacket constructor(
......@@ -66,9 +63,10 @@ open class ClientLoginResendPacket constructor(
private val privateKey: ByteArray,
private val token0825: ByteArray,
private val token00BA: ByteArray,
private val tlv0006: ByteArray? = null
private val randomDeviceName: Boolean = false,
private val tlv0006: IoBuffer? = null
) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.passwordSubmissionTLV1)
......@@ -79,13 +77,14 @@ open class ClientLoginResendPacket constructor(
this.writeHex(TIMProtocol.key0836)//16
this.encryptAndWrite(TIMProtocol.shareKey.hexToBytes()) {
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, tlv0006)
writePart1(qq, password, loginTime, loginIP, privateKey, token0825, randomDeviceName, tlv0006)
writeHex("01 10") //tag
writeHex("00 3C")//length
writeHex("00 01")//tag
writeHex("00 38")//length
write(token00BA)//value
writeHex("01 10")
writeHex("00 3C")
writeHex("00 01")
writeHex("00 38")
writeFully(token00BA)
writePart2()
}
......@@ -93,32 +92,40 @@ open class ClientLoginResendPacket constructor(
}
/**
* @author Him188moe
*/
private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray, token0825: ByteArray, tlv0006: ByteArray? = null) {
private fun BytePacketBuilder.writePart1(
qq: Long,
password: String,
loginTime: Int,
loginIP: String,
privateKey: ByteArray,
token0825: ByteArray,
randomDeviceName: Boolean,
tlv0006: IoBuffer? = null
) {
//this.writeInt(System.currentTimeMillis().toInt())
this.writeHex("01 12")//tag
this.writeHex("00 38")//length
this.write(token0825)//length
this.writeFully(token0825)//length
this.writeHex("03 0F")//tag
this.writeDeviceName(false)
this.writeDeviceName(randomDeviceName)
this.writeHex("00 05 00 06 00 02")
this.writeQQ(qq)
this.writeHex("00 06")//tag
this.writeHex("00 78")//length
if (tlv0006 != null) {
this.write(tlv0006)
MiraiLogger.logDebug("tlv0006!=null")
this.writeFully(tlv0006)
} else {
MiraiLogger.logDebug("tlv0006==null")
this.writeTLV0006(qq, password, loginTime, loginIP, privateKey)
}
//fix
this.writeHex(TIMProtocol.passwordSubmissionTLV2)
this.writeHex("00 1A")//tag
this.writeHex("00 40")//length
this.write(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), privateKey))
this.writeFully(TEA.encrypt(TIMProtocol.passwordSubmissionTLV2.hexToBytes(), privateKey))
this.writeHex(TIMProtocol.constantData1)
this.writeHex(TIMProtocol.constantData2)
this.writeQQ(qq)
......@@ -133,7 +140,7 @@ private fun DataOutputStream.writePart1(qq: Long, password: String, loginTime: I
}
private fun DataOutputStream.writePart2() {
private fun BytePacketBuilder.writePart2() {
this.writeHex("03 12")//tag
this.writeHex("00 05")//length
......@@ -161,9 +168,8 @@ private fun DataOutputStream.writePart2() {
//value
this.writeHex("E9 AA 2B 4D 26 4C 76 18 FE 59 D5 A9 82 6A 0C 04 B4 49 50 D7 9B B1 FE 5D 97 54 8D 82 F3 22 C2 48 B9 C9 22 69 CA 78 AD 3E 2D E9 C9 DF A8 9E 7D 8C 8D 6B DF 4C D7 34 D0 D3")
this.writeHex("00 14")//length
writeCRC32()
this.writeHex("00 14")
this.writeCRC32()
}
package net.mamoe.mirai.network.protocol.tim.packet.login
/**
* @author Him188moe
*/
enum class LoginState {
enum class LoginResult {
/**
* 登录成功
*/
......@@ -39,6 +36,11 @@ enum class LoginState {
*/
UNKNOWN,
/**
* 未知. 更换服务器或等几分钟再登录可能解决.
*/
INTERNAL_ERROR,
/**
* 超时
*/
......
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import java.io.DataInputStream
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* SKey 用于 http api
*
* @author Him188moe
*/
@PacketId("00 1D")
class ClientSKeyRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer2)
this.encryptAndWrite(sessionKey) {
override fun encode(builder: BytePacketBuilder) = with(builder) {
writeQQ(qq)
writeHex(TIMProtocol.fixVer2)
encryptAndWrite(sessionKey) {
writeHex("33 00 05 00 08 74 2E 71 71 2E 63 6F 6D 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 0C 6A 75 62 61 6F 2E 71 71 2E 63 6F 6D 00 09 6B 65 2E 71 71 2E 63 6F 6D")
}
}
}
/**
* @author Him188moe
*/
@PacketId("00 1D")
class ClientSKeyRefreshmentRequestPacket(
private val qq: Long,
private val sessionKey: ByteArray
) : ClientPacket() {
override fun encode() {
this.writeRandom(2)//part of packet id
override val idHex: String by lazy {
super.idHex + " " + getRandomByteArray(2).toUHexString()
}
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.encryptAndWrite(sessionKey) {
writeHex("33 00 05 00 08 74 2E 71 71 2E 63 6F 6D 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 0C 6A 75 62 61 6F 2E 71 71 2E 63 6F 6D 00 09 6B 65 2E 71 71 2E 63 6F 6D")
......@@ -45,18 +48,16 @@ class ClientSKeyRefreshmentRequestPacket(
}
}
/**
* @author Him188moe
*/
class ServerSKeyResponsePacket(input: DataInputStream) : ServerPacket(input) {
class ServerSKeyResponsePacket(input: ByteReadPacket) : ServerPacket(input) {
lateinit var sKey: String
override fun decode() {
this.sKey = String(this.input.goto(4).readNBytes(10))
override fun decode() = with(input) {
discardExact(4)
sKey = this.readString(10)//todo test
MiraiLogger.logDebug("SKey=$sKey")
}
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionKey: ByteArray): ServerSKeyResponsePacket = ServerSKeyResponsePacket(this.decryptBy(sessionKey)).setId(this.idHex)
}
}
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
import kotlin.properties.Delegates
sealed class ServerLoginResponsePacket(input: ByteReadPacket) : ServerPacket(input)
class ServerLoginResponseFailedPacket(val loginResult: LoginResult, input: ByteReadPacket) : ServerLoginResponsePacket(input)
/**
* 服务器进行加密后返回 privateKey
*
* @author NaturalHG
*/
@PacketId("08 36 31 03")
class ServerLoginResponseKeyExchangePacket(input: ByteReadPacket, val flag: Flag) : ServerLoginResponsePacket(input) {
enum class Flag {
`08 36 31 03`,
OTHER,
}
lateinit var tlv0006: IoBuffer//120bytes
var tokenUnknown: ByteArray? = null
lateinit var privateKeyUpdate: ByteArray//16bytes
@Tested
override fun decode() {
this.input.discardExact(5)//01 00 1E 00 10
privateKeyUpdate = this.input.readBytes(0x10)//22
this.input.discardExact(4)//00 06 00 78
tlv0006 = this.input.readIoBuffer(0x78)
when (flag) {
Flag.`08 36 31 03` -> {//TODO 在解析时分类而不是在这里
this.input.discardExact(8)//01 10 00 3C 00 01 00 38
tokenUnknown = this.input.readBytes(56)
//println(tokenUnknown!!.toUHexString())
}
Flag.OTHER -> {
//do nothing in this packet.
//[this.token] will be set in [BotNetworkHandler]
//token
}
}
}
class Encrypted(input: ByteReadPacket, private val flag: Flag) : ServerPacket(input) {
@Tested
fun decrypt(privateKey: ByteArray): ServerLoginResponseKeyExchangePacket = ServerLoginResponseKeyExchangePacket(this.decryptBy(TIMProtocol.shareKey, privateKey), flag).setId(this.idHex)
}
}
enum class Gender(val id: Boolean) {
MALE(false),
FEMALE(true);
}
/**
* @author NaturalHG
*/
class ServerLoginResponseSuccessPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var sessionResponseDecryptionKey: IoBuffer//16 bytes|
lateinit var token38: IoBuffer//56
lateinit var token88: IoBuffer//136
lateinit var encryptionKey: IoBuffer//16
lateinit var nickname: String
var age: Short by Delegates.notNull()
lateinit var gender: Gender
@Tested
override fun decode() = with(input) {
discardExact(7)//00 01 09 00 70 00 01
encryptionKey = readIoBuffer(16)//C6 72 C7 73 70 01 46 A2 11 88 AC E4 92 7B BF 90
discardExact(2)//00 38
token38 = readIoBuffer(56)
discardExact(60)//00 20 01 60 C5 A1 39 7A 12 8E BC 34 C3 56 70 E3 1A ED 20 67 ED A9 DB 06 C1 70 81 3C 01 69 0D FF 63 DA 00 00 01 03 00 14 00 01 00 10 60 C9 5D A7 45 70 04 7F 21 7D 84 50 5C 66 A5 C6
discardExact(when (val flag = readBytes(2).toUHexString()) {
"01 07" -> 0
"00 33" -> 28
"01 10" -> 64
else -> throw IllegalStateException(flag)
})
discardExact(23 + 3)//01 D3 00 01 00 16 00 00 00 01 00 00 00 64 00 00 0D DE 00 09 3A 80 00
discardExact(2)//00 02
sessionResponseDecryptionKey = readIoBuffer(16)
discardExact(2)
token88 = readIoBuffer(136)
discardExact(299)//2E 72 7A 50 41 54 5B 62 7D 47 5D 37 41 53 47 51 00 78 00 01 5D A2 DB 79 00 70 72 E7 D3 4E 6F D8 D1 DD F2 67 04 1D 23 4D E9 A7 AB 89 7A B7 E6 4B C0 79 60 3B 4F AA 31 C5 24 51 C1 4B 4F A4 32 74 BA FE 8E 06 DB 54 25 A2 56 91 E8 66 BB 23 29 EB F7 13 7B 94 1E AF B2 40 4E 69 5C 8C 35 04 D1 25 1F 60 93 F3 40 71 0B 61 60 F1 B6 A9 7A E8 B1 DA 0E 16 A2 F1 2D 69 5A 01 20 7A AB A7 37 68 D2 1A B0 4D 35 D1 E1 35 64 F6 90 2B 00 83 01 24 5B 4E 69 3D 45 54 6B 29 5E 73 23 2D 4E 42 3F 00 70 00 01 5D A2 DB 79 00 68 FD 10 8A 39 51 09 C6 69 CE 09 A4 52 8C 53 D3 B6 87 E1 7B 7E 4E 52 6D BA 9C C4 6E 6D DE 09 99 67 B4 BD 56 71 14 5A 54 01 68 1C 3C AA 0D 76 0B 86 5A C1 F1 BC 5E 0A ED E3 8C 57 86 35 D8 A5 F8 16 01 24 8B 57 56 8C A6 31 6F 65 73 03 DA ED 21 FA 6B 79 32 2B 09 01 E8 D2 D8 F0 7B F1 60 C2 7F 53 5D F6 53 50 8A 43 E2 23 2E 52 7B 60 39 56 67 2D 6A 23 43 4B 60 55 68 35 01 08 00 23 00 01 00 1F 00 17 02 5B
val nickLength = readUByte().toInt()
nickname = readString(nickLength)
//后文
//00 05 00 04 00 00 00 01 01 15 00 10 49 83 5C D9 93 6C 8D FE 09 18 99 37 99 80 68 92
discardExact(4)//02 13 80 02
age = readShort()//00 05
discardExact(4)//00 04 00 00
discardExact(2)//00 01
gender = if (readBoolean()) Gender.FEMALE else Gender.MALE
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(privateKey: ByteArray): ServerLoginResponseSuccessPacket = ServerLoginResponseSuccessPacket(this.decryptBy(TIMProtocol.shareKey, privateKey)).setId(this.idHex)
}
}
/**
* 收到这个包意味着需要验证码登录, 并且能得到验证码图片文件的一部分
*
* @author Him188moe
*/
class ServerLoginResponseVerificationCodeInitPacket(input: ByteReadPacket) : ServerLoginResponsePacket(input) {
lateinit var verifyCodePart1: IoBuffer
lateinit var token00BA: ByteArray
var unknownBoolean: Boolean? = null
@Tested
override fun decode() {
this.input.discardExact(78)
//println(this.input.readRemainingBytes().toUHexString())
val verifyCodeLength = this.input.readShort()//2bytes
this.verifyCodePart1 = this.input.readIoBuffer(verifyCodeLength)
this.input.discardExact(1)
this.unknownBoolean = this.input.readByte().toInt() == 1
this.input.discardExact(this.input.remaining - 60)
this.token00BA = this.input.readBytes(40)
}
class Encrypted(input: ByteReadPacket) : ServerPacket(input) {
fun decrypt(): ServerLoginResponseVerificationCodeInitPacket = ServerLoginResponseVerificationCodeInitPacket(this.decryptAsByteArray(TIMProtocol.shareKey).toReadPacket()).setId(this.idHex)
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import java.io.DataInputStream
/**
* Congratulations!
......@@ -10,4 +10,4 @@ import java.io.DataInputStream
* @author Him188moe
*/
@PacketId("00 EC")
class ServerLoginSuccessPacket(input: DataInputStream) : ServerPacket(input)
\ No newline at end of file
class ServerLoginSuccessPacket(input: ByteReadPacket) : ServerPacket(input)
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.dataEncode
import java.io.DataInputStream
import java.net.InetAddress
/**
* @author Him188moe
*/
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
@PacketId("08 28 04 34")
class ClientSessionRequestPacket(
private val qq: Long,
private val serverIp: String,
private val token38: ByteArray,
private val token88: ByteArray,
private val encryptionKey: ByteArray
private val token38: IoBuffer,
private val token88: IoBuffer,
private val encryptionKey: IoBuffer
) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex("02 00 00 00 01 2E 01 00 00 68 52 00 30 00 3A")
this.writeHex("00 38")
this.write(token38)
this.writeFully(token38)
this.encryptAndWrite(encryptionKey) {
writeHex("00 07 00 88")
write(token88)
writeFully(token88)
writeHex("00 0C 00 16 00 02 00 00 00 00 00 00 00 00 00 00")
writeIP(serverIp)
writeHex("1F 40 00 00 00 00 00 15 00 30 00 01")//fix1
......@@ -56,44 +54,51 @@ class ClientSessionRequestPacket(
writeHex("68")
writeHex("00 00 00 00 00 2D 00 06 00 01")
writeIP(InetAddress.getLocalHost().hostAddress)//todo random to avoid being banned?
writeIP(localIpAddress())//todo random to avoid being banned?
}
}
}
/**
* @author Him188moe
*/
@PacketId("08 28 04 34")
class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val dataLength: Int) : ServerPacket(inputStream) {
class ServerSessionKeyResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
lateinit var sessionKey: ByteArray
lateinit var tlv0105: ByteArray
override fun decode() {
when (dataLength) {
407 -> {
input.goto(25)
sessionKey = input.readNBytes(16)
lateinit var tlv0105: ByteReadPacket
@Tested
override fun decode() = with(input) {
when (val dataLength = remaining) {
407L -> {
input.discardExact(25)//todo test
sessionKey = input.readBytes(16)
}
439 -> {
input.goto(63)
sessionKey = input.readNBytes(16)
439L -> {
input.discardExact(63)
sessionKey = input.readBytes(16)
}
512,
527 -> {
input.goto(63)
sessionKey = input.readNBytes(16)
tlv0105 = dataEncode {
it.writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00")
input.goto(dataLength - 122)
it.write(input.readNBytes(56))
it.writeHex("00 40 02 02 03 3C 01 03 00 00")
input.goto(dataLength - 55)
it.write(input.readNBytes(56))
502L,//?
512L,
527L -> {
input.discardExact(63)//00 00 0D 00 06 00 01 00 00 00 00 00 1F 00 22 00 01 D7 EC FC 38 1B 74 6F 91 42 00 B9 DB 69 32 43 EC 8C 02 DC E0 07 35 58 8C 6C FE 43 5D AA 6A 88 E0 00 14 00 04 00 01 00 3C 01 0C 00 73 00 01
sessionKey = input.readBytes(16)
tlv0105 = buildPacket {
writeHex("01 05 00 88 00 01 01 02 00 40 02 01 03 3C 01 03 00 00")
input.discardExact(input.remaining - 122 - 1)
writeFully(input.readIoBuffer(56))
writeHex("00 40 02 02 03 3C 01 03 00 00")
input.discardExact(11)
writeFully(input.readIoBuffer(56))
} //todo 这个 tlv0105似乎可以保存起来然后下次登录时使用.
/*
Discarded(63) =00 00 0D 00 06 00 01 00 00 00 00 00 1F 00 22 00 01 F7 AB 01 4B 23 B5 47 FC 79 02 09 E0 19 EF 61 91 14 AD 8F 38 2E 8B D7 47 39 DE FE 84 A7 E5 6E 3D 00 14 00 04 00 01 00 3C 01 0C 00 73 00 01
sessionKey=7E 8C 1D AC 52 64 B8 D0 9A 55 3A A6 DF 53 88 C8
Discarded(301) =76 E4 B8 DD AB 53 02 2B 53 F1 5D A2 DA CB 00 00 00 B4 03 3D 97 B4 D1 3D 97 B4 C7 00 00 00 07 00 30 D4 E2 53 73 2E 00 F6 3F 8E 45 9F 2E 74 63 39 99 B4 AC 3B 40 C8 9A EE B0 62 A8 E1 39 FE 8E 75 EC 28 6C 03 E6 3B 5F F5 6D 50 7D 1E 29 EC 3D 47 85 08 02 04 08 08 08 08 08 04 00 05 01 0E 12 AC F6 01 0E 00 56 00 01 00 52 13 80 42 00 00 02 02 00 00 18 AB 52 CF 5B E8 CD 95 CC 3F 5C A7 BA C9 C1 5D DD F8 E2 6E 0D A3 DF F8 76 00 20 D3 87 6B 1F F2 2B C7 53 38 60 F3 AD 07 82 8B F6 62 3C E0 DB 66 BC AD D0 68 D0 30 9D 8A 41 E7 75 00 0C 00 00 00 01 00 00 00 00 00 00 00 40 00 2F 00 2A 00 01 8F FE 4F BB B2 63 C7 69 C3 F1 3C DC A1 E8 77 A3 DD 97 FA 00 36 04 40 EF 11 7A 31 02 4E 10 13 94 02 28 00 00 00 00 00 00 01 0D 00 2C 00 01 00 28 EF CB 22 58 6F AE DC F5 CC CE 45 EE 6D CA E7 EF 06 3F 60 B5 8A 22 D5 9E 37 FA 92 9F A9 11 68 F0 2A 25 4A 45 C3 D4 56 CF 01 05 00 8A 00 01 02 02 00 41 01 00 01 03 3C 01 03 00 00 FB
56长度=39 89 04 81 64 6B C0 71 B5 6E B0 DF 7D D4 C0 7E 97 83 BC 9F 31 39 39 C3 95 93 D9 CD 48 00 1D 0D 18 52 87 21 B2 C1 B1 AD EF 96 82 D6 D4 57 EA 48 5A 27 8C 14 6F E2 83 00
Discarded(11) =41 01 00 02 03 3C 01 03 00 00 86
*/
}
else -> throw IllegalArgumentException(dataLength.toString())
......@@ -104,9 +109,8 @@ class ServerSessionKeyResponsePacket(inputStream: DataInputStream, private val d
}
class Encrypted(inputStream: DataInputStream) : ServerPacket(inputStream) {
fun decrypt(sessionResponseDecryptionKey: ByteArray): ServerSessionKeyResponsePacket = this.decryptAsByteArray(sessionResponseDecryptionKey).let {
ServerSessionKeyResponsePacket(it.dataInputStream(), it.size).setId(this.idHex)
}
class Encrypted(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(sessionResponseDecryptionKey: IoBuffer): ServerSessionKeyResponsePacket =
ServerSessionKeyResponsePacket(this.decryptBy(sessionResponseDecryptionKey)).setId(this.idHex)
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet.login
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.utils.hexToBytes
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
import java.io.IOException
import net.mamoe.mirai.network.protocol.tim.packet.ClientPacket
import net.mamoe.mirai.network.protocol.tim.packet.PacketId
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.network.protocol.tim.packet.setId
import net.mamoe.mirai.utils.*
/**
* The packet received when logging in, used to redirect server address
......@@ -17,7 +21,7 @@ import java.io.IOException
*/
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
@PacketId("08 25 31 01")
class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inputStream) {
class ServerTouchResponsePacket(inputStream: ByteReadPacket) : ServerPacket(inputStream) {
var serverIP: String? = null
var loginTime: Int = 0
......@@ -30,28 +34,28 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
}
override fun decode() {
when (val id = input.readByte().toUByte().toInt()) {
0xFE -> {
input.skip(94)
serverIP = input.readIP()
override fun decode() = with(input) {
when (val id = readByte().toUByte().toInt()) {
0xFE -> {//todo 在 packet 解析时分类而不是在这里分类
discardExact(94)
serverIP = readIP()
}
0x00 -> {
input.skip(4)
token0825 = input.readNBytes(56)
input.skip(6)
discardExact(4)
token0825 = readBytes(56)
discardExact(6)
loginTime = input.readInt()
loginIP = input.readIP()
loginTime = readInt()
loginIP = readIP()
}
else -> {
throw IllegalStateException(arrayOf(id.toUByte()).toUByteArray().toUHexString())
throw IllegalStateException(id.toByte().toUHexString())
}
}
}
class Encrypted(private val type: Type, inputStream: DataInputStream) : ServerPacket(inputStream) {
class Encrypted(private val type: Type, inputStream: ByteReadPacket) : ServerPacket(inputStream) {
fun decrypt(): ServerTouchResponsePacket = ServerTouchResponsePacket(decryptBy(when (type) {
Type.TYPE_08_25_31_02 -> TIMProtocol.redirectionKey.hexToBytes()
......@@ -61,16 +65,13 @@ class ServerTouchResponsePacket(inputStream: DataInputStream) : ServerPacket(inp
}
/**
* The packet to touch server, that is, to start the connection to the server.
* The packet to sendTouch server, that is, to start the connection to the server.
*
* @author Him188moe
*/
@PacketId("08 25 31 01")
class ClientTouchPacket(private val qq: Long, private val serverIp: String) : ClientPacket() {
@Throws(IOException::class)
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.touchKey)
......@@ -94,13 +95,11 @@ class ClientTouchPacket(private val qq: Long, private val serverIp: String) : Cl
*/
@PacketId("08 25 31 02")
class ClientServerRedirectionPacket(private val serverIP: String, private val qq: Long) : ClientPacket() {
override fun encode() {
override fun encode(builder: BytePacketBuilder) = with(builder) {
this.writeQQ(qq)
this.writeHex(TIMProtocol.fixVer)
this.writeHex(TIMProtocol.redirectionKey)
this.encryptAndWrite(TIMProtocol.redirectionKey) {
this.writeHex(TIMProtocol.constantData1)
this.writeHex(TIMProtocol.constantData2)
......
package net.mamoe.mirai.utils
/**
* @author Him188moe
*/
data class BotAccount(
val qqNumber: Long,//实际上是 UInt
val password: String//todo 不保存 password?
......
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.String
import kotlin.jvm.JvmOverloads
@JvmOverloads
fun ByteArray.toHexString(separator: String = " "): String = this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
}
return@joinToString ret
}
@JvmOverloads
fun ByteArray.toUHexString(separator: String = " "): String = this.toUByteArray().toUHexString(separator)
fun ByteArray.stringOf(): String = String(this)
//@JvmSynthetic TODO 等待 kotlin 修复 bug 后添加这个注解
@JvmOverloads
fun UByteArray.toUHexString(separator: String = " "): String = this.joinToString(separator) {
var ret = it.toString(16).toUpperCase()
if (ret.length == 1) {
ret = "0$ret"
}
return@joinToString ret
}
fun ByteArray.toReadPacket() = ByteReadPacket(this)
fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R = this.toReadPacket().run(t)
fun ByteArray.cutTail(length: Int): ByteArray = this.copyOfRange(0, this.size - length)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import net.mamoe.mirai.network.protocol.tim.packet.*
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerCanAddFriendResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendFriendMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.action.ServerSendGroupMessageResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.login.*
fun ByteReadPacket.readRemainingBytes(
n: Int = remaining.toInt()//not that safe but adequate
): ByteArray = ByteArray(n).also { readAvailable(it, 0, n) }
fun ByteReadPacket.readIoBuffer(
n: Int = remaining.toInt()//not that safe but adequate
): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) }
fun ByteReadPacket.readIoBuffer(n: Number) = this.readIoBuffer(n.toInt())
//必须消耗完 packet
fun ByteReadPacket.parseServerPacket(size: Int): ServerPacket {//TODO 优化
discardExact(3)
val idHex = readInt().toUHexString(" ")
discardExact(7)//4 for qq number, 3 for 0x00 0x00 0x00. 但更可能是应该 discard 8
return when (idHex) {
"08 25 31 01" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_01, this)
"08 25 31 02" -> ServerTouchResponsePacket.Encrypted(ServerTouchResponsePacket.Type.TYPE_08_25_31_02, this)
"08 36 31 03", "08 36 31 04", "08 36 31 05", "08 36 31 06" -> {
when (size) {
271, 207 -> return ServerLoginResponseKeyExchangePacket.Encrypted(this, when (idHex) {
"08 36 31 03" -> ServerLoginResponseKeyExchangePacket.Flag.`08 36 31 03`
else -> ServerLoginResponseKeyExchangePacket.Flag.OTHER
}).setId(idHex)
871 -> return ServerLoginResponseVerificationCodeInitPacket.Encrypted(this).setId(idHex)
}
if (size > 700) {
return ServerLoginResponseSuccessPacket.Encrypted(this).setId(idHex)
}
println(size)
return ServerLoginResponseFailedPacket(when (size) {
135 -> {//包数据错误. 目前怀疑是 tlv0006
this.readRemainingBytes().cutTail(1).decryptBy(TIMProtocol.shareKey).read {
discardExact(51)
MiraiLogger.logError("Internal logError: " + readLVString())//抱歉,请重新输入密码。
}
LoginResult.INTERNAL_ERROR
} //可能是包数据错了. 账号没有被ban, 用TIM官方可以登录
319, 351 -> LoginResult.WRONG_PASSWORD
//135 -> LoginState.RETYPE_PASSWORD
63, 279 -> LoginResult.BLOCKED
263 -> LoginResult.UNKNOWN_QQ_NUMBER
551, 487 -> LoginResult.DEVICE_LOCK
343, 359 -> LoginResult.TAKEN_BACK
else -> LoginResult.UNKNOWN
/*
//unknown
63 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown logError)")
351 -> throw IllegalArgumentException(bytes.size.toString() + " (Unknown logError)")
else -> throw IllegalArgumentException(bytes.size.toString())*/
}, this).setId(idHex)
}
"08 28 04 34" -> ServerSessionKeyResponsePacket.Encrypted(this)
else -> when (idHex.substring(0, 5)) {
"00 EC" -> ServerLoginSuccessPacket(this)
"00 1D" -> ServerSKeyResponsePacket.Encrypted(this)
"00 5C" -> ServerAccountInfoResponsePacket.Encrypted(this)
"00 58" -> ServerHeartbeatResponsePacket(this)
"00 BA" -> ServerCaptchaPacket.Encrypted(this, idHex)
"00 CE", "00 17" -> ServerEventPacket.Raw.Encrypted(this, idHex.hexToBytes())
"00 81" -> ServerFieldOnlineStatusChangedPacket.Encrypted(this)
"00 CD" -> ServerSendFriendMessageResponsePacket(this)
"00 02" -> ServerSendGroupMessageResponsePacket(this)
"00 A7" -> ServerCanAddFriendResponsePacket(this)
"03 88" -> ServerTryGetImageIDResponsePacket.Encrypted(this)
else -> UnknownServerPacket.Encrypted(this)
}
}.setId(idHex)
}
fun ByteReadPacket.readIP(): String = buildString(4 + 3) {
repeat(4) {
val byte = readUByte()
this.append(byte.toString())
if (it != 3) this.append(".")
}
}
fun ByteReadPacket.readLVString(): String = String(this.readLVByteArray())
fun ByteReadPacket.readLVByteArray(): ByteArray = this.readBytes(this.readShort().toInt())
fun ByteReadPacket.readTLVMap(expectingEOF: Boolean = false): Map<Int, ByteArray> {
val map = mutableMapOf<Int, ByteArray>()
var type: UByte
try {
type = readUByte()
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
while (type != UByte.MAX_VALUE) {
map[type.toInt()] = this.readLVByteArray()
try {
type = readUByte()
} catch (e: EOFException) {
if (expectingEOF) {
return map
}
throw e
}
}
return map
}
fun Map<Int, ByteArray>.printTLVMap(name: String) = debugPrintln("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() })
fun ByteReadPacket.readString(length: Number): String = String(this.readBytes(length.toInt()))
private const val TRUE_BYTE_VALUE: Byte = 1
fun ByteReadPacket.readBoolean(): Boolean = this.readByte() == TRUE_BYTE_VALUE
fun ByteReadPacket.readLVNumber(): Number {
return when (this.readShort().toInt()) {
1 -> this.readByte()
2 -> this.readShort()
4 -> this.readInt()
8 -> this.readLong()
else -> throw UnsupportedOperationException()
}
}
//添加@JvmSynthetic 导致 idea 无法检查这个文件的错误
//@JvmSynthetic
@Deprecated("Low efficiency", ReplaceWith(""))
fun ByteReadPacket.gotoWhere(matcher: UByteArray): ByteReadPacket {
return this.gotoWhere(matcher.toByteArray())
}
/**
* 去往下一个含这些连续字节的位置
*/
@Deprecated("Low efficiency", ReplaceWith(""))
fun ByteReadPacket.gotoWhere(matcher: ByteArray): ByteReadPacket {
require(matcher.isNotEmpty())
loop@
do {
val byte = this.readByte()
if (byte == matcher[0]) {
//todo mark here
for (i in 1 until matcher.size) {
val b = this.readByte()
if (b != matcher[i]) {
continue@loop //todo goto mark
}
}
return this
}
} while (true)
}
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully
private const val GTK_BASE_VALUE: Int = 5381
@ExperimentalStdlibApi
internal fun getGTK(sKey: String): Int {
var value = GTK_BASE_VALUE
for (c in sKey.toCharArray()) {
value += (value shl 5) + c.toInt()
}
value = value and Int.MAX_VALUE
return value
}
@Tested
fun BytePacketBuilder.writeCRC32() = writeCRC32(getRandomByteArray(16))
fun BytePacketBuilder.writeCRC32(key: ByteArray) {
writeFully(key)//key
writeInt(crc32(key))
}
fun md5(str: String): ByteArray = md5(str.toByteArray())
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
/**
* 让用户处理验证码
*
* @return 用户输入得到的验证码. 非 null 时一定 `length==4`.
*/
internal expect suspend fun solveCaptcha(captchaBuffer: IoBuffer): String?
......@@ -2,7 +2,5 @@ package net.mamoe.mirai.utils
import net.mamoe.mirai.contact.Contact
/**
* @author Him188moe
*/
class ContactList<C : Contact> : MutableMap<Long, C> by mutableMapOf()
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.Input
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
internal object DebugLogger : MiraiLogger by PlatformLogger("Packet Debug")
internal fun debugPrintln(any: Any?) = DebugLogger.logPurple(any)
@Deprecated("Debugging Warning", ReplaceWith(""))
internal fun ByteArray.debugPrint(name: String): ByteArray {
DebugLogger.logPurple(name + "=" + this.toUHexString())
return this
}
@Deprecated("Debugging Warning", ReplaceWith(""))
internal fun IoBuffer.debugPrint(name: String): IoBuffer {
val readBytes = this.readBytes()
DebugLogger.logPurple(name + "=" + readBytes.toUHexString())
return readBytes.toIoBuffer()
}
@Deprecated("Debugging Warning", ReplaceWith(""))
internal fun Input.debugDiscardExact(n: Number, name: String = "") {
DebugLogger.logPurple("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString())
}
package net.mamoe.mirai.utils
inline fun <T> MutableIterable<T>.removeIfInlined(predicate: (T) -> Boolean) = iterator().removeIfInlined(predicate)
inline fun <T> MutableIterator<T>.removeIfInlined(predicate: (T) -> Boolean) {
while (this.hasNext()) {
if (predicate(this.next())) {
this.remove()
}
}
}
\ No newline at end of file
package net.mamoe.mirai.utils
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerTouchResponsePacket
class LoginConfiguration {
/**
* 等待 [ServerTouchResponsePacket] 的时间
*/
var touchTimeoutMillis: Long = 2000
var randomDeviceName: Boolean = false
companion object {
val Default = LoginConfiguration()
}
}
package net.mamoe.mirai.utils
import kotlin.jvm.JvmOverloads
interface MiraiLogger {
companion object : MiraiLogger by PlatformLogger("[TOP Level]")
var identity: String?
fun logInfo(any: Any?) = log(any)
fun log(e: Throwable)
fun log(any: Any?)
fun logError(any: Any?)
fun logDebug(any: Any?)
fun logCyan(any: Any?)
fun logPurple(any: Any?)
fun logGreen(any: Any?)
fun logBlue(any: Any?)
}
expect class PlatformLogger @JvmOverloads constructor(identity: String? = null) : MiraiLogger
fun Throwable.log() = MiraiLogger.log(this)
fun Throwable.printStacktrace() = this.log()
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
/**
......@@ -7,12 +8,21 @@ package net.mamoe.mirai.utils
* @author Him188moe
* @see net.mamoe.mirai.network.protocol.tim.packet.login.ClientChangeOnlineStatusPacket
*/
enum class ClientLoginStatus(
// TODO: 2019/8/31 add more ClientLoginStatus
enum class OnlineStatus(
val id: UByte//1 ubyte
) {
/**
* 我在线上
*/
ONLINE(0x0Au)
ONLINE(0x0Au),
/**
* 忙碌
*/
BUSY(0x32u);
companion object {
fun ofId(id: UByte): OnlineStatus = values().first { it.id == id }
}
}
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.*
import net.mamoe.mirai.network.protocol.tim.TIMProtocol
import kotlin.random.Random
import kotlin.random.nextInt
fun BytePacketBuilder.writeZero(count: Int) = repeat(count) { this.writeByte(0) }
fun BytePacketBuilder.writeRandom(length: Int) = repeat(length) { this.writeByte(Random.Default.nextInt(255).toByte()) }
fun BytePacketBuilder.writeQQ(qq: Long) = this.writeUInt(qq.toUInt())
fun BytePacketBuilder.writeGroup(groupIdOrGroupNumber: Long) = this.writeFully(groupIdOrGroupNumber.toUInt().toByteArray())
fun BytePacketBuilder.writeLVByteArray(byteArray: ByteArray) {
this.writeShort(byteArray.size.toShort())
this.writeFully(byteArray)
}
fun BytePacketBuilder.writeLVPacket(packet: ByteReadPacket) {
this.writeShort(packet.remaining.toShort())
this.writePacket(packet)
}
fun BytePacketBuilder.writeLVPacket(builder: BytePacketBuilder.() -> Unit) = this.writeLVPacket(BytePacketBuilder().apply(builder).use { it.build() })
@Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVString(str: String) = this.writeLVByteArray(str.toByteArray())
@Suppress("DEPRECATION")
fun BytePacketBuilder.writeLVHex(hex: String) = this.writeLVByteArray(hex.hexToBytes())
fun BytePacketBuilder.writeIP(ip: String) = writeFully(ip.trim().split(".").map { it.toUByte() }.toUByteArray())
fun BytePacketBuilder.writeTime() = this.writeInt(currentTime.toInt())
fun BytePacketBuilder.writeHex(uHex: String) = this.writeFully(uHex.hexToUBytes())
fun BytePacketBuilder.encryptAndWrite(key: IoBuffer, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(key.readBytes(), encoder)
fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = writeFully(TEA.encrypt(BytePacketBuilder().apply(encoder).use { it.build().readBytes() }, key))
fun BytePacketBuilder.encryptAndWrite(keyHex: String, encoder: BytePacketBuilder.() -> Unit) = encryptAndWrite(keyHex.hexToBytes(), encoder)
fun BytePacketBuilder.writeTLV0006(qq: Long, password: String, loginTime: Int, loginIP: String, privateKey: ByteArray) {
val firstMD5 = md5(password)
val secondMD5 = md5(firstMD5 + byteArrayOf(0, 0, 0, 0) + qq.toUInt().toByteArray())
this.encryptAndWrite(secondMD5) {
writeRandom(4)
writeHex("00 02")
writeQQ(qq)
writeHex(TIMProtocol.constantData2)
writeHex("00 00 01")
writeFully(firstMD5)
writeInt(loginTime)
writeByte(0)
writeZero(4 * 3)
writeIP(loginIP)
writeZero(8)
writeHex("00 10")//这两个hex是passwordSubmissionTLV2的末尾
writeHex("15 74 C4 89 85 7A 19 F5 5E A9 C9 A3 5E 8A 5A 9B")//16
writeFully(privateKey)
}
}
@Tested
fun BytePacketBuilder.writeDeviceName(random: Boolean) {
val deviceName: String = if (random) {
"DESKTOP-" + String(ByteArray(7) {
(if (Random.nextBoolean()) Random.nextInt('A'.toInt()..'Z'.toInt())
else Random.nextInt('1'.toInt()..'9'.toInt())).toByte()
})
} else {
deviceName
}
this.writeShort((deviceName.length + 2).toShort())
this.writeShort(deviceName.length.toShort())
this.writeStringUtf8(deviceName)//TODO TEST?
}
\ No newline at end of file
package net.mamoe.mirai.utils
/**
* 时间戳
*/
expect val currentTime: Long
/**
* 设备名
*/
expect val deviceName: String
/**
* CRC32 算法
*/
expect fun crc32(key: ByteArray): Int
/**
* MD5 算法
*
* @return 16 bytes
*/
expect fun md5(byteArray: ByteArray): ByteArray
/**
* Hostname 解析 IP 地址
*/
expect fun solveIpAddress(hostname: String): String
/**
* Localhost 解析
*/
expect fun localIpAddress(): String
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.Closeable
import kotlinx.io.core.IoBuffer
import kotlinx.io.errors.IOException
expect class MiraiDatagramChannel(serverHost: String, serverPort: Short) : Closeable {
suspend fun read(buffer: IoBuffer): Int
suspend fun send(buffer: IoBuffer): Int
val isOpen: Boolean
}
expect class ClosedChannelException : IOException
\ No newline at end of file
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import kotlin.jvm.JvmStatic
expect object TEA {
internal fun doOption(data: ByteArray, key: ByteArray, encrypt: Boolean): ByteArray
@JvmStatic
fun encrypt(source: ByteArray, key: ByteArray): ByteArray
@JvmStatic
fun decrypt(source: ByteArray, key: ByteArray): ByteArray
@JvmStatic
fun decrypt(source: ByteArray, key: IoBuffer): ByteArray
@JvmStatic
fun decrypt(source: ByteArray, keyHex: String): ByteArray
}
fun ByteArray.decryptBy(key: ByteArray): ByteArray = TEA.decrypt(this, key)
fun ByteArray.decryptBy(key: IoBuffer): ByteArray = TEA.decrypt(this, key)
fun ByteArray.decryptBy(key: String): ByteArray = TEA.decrypt(this, key)
\ No newline at end of file
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.writeFully
import kotlin.jvm.Synchronized
import kotlin.random.Random
import kotlin.random.nextInt
/**
* 255 -> 00 00 00 FF
*/
fun Int.toByteArray(): ByteArray = byteArrayOf(
(this.ushr(24) and 0xFF).toByte(),
(this.ushr(16) and 0xFF).toByte(),
(this.ushr(8) and 0xFF).toByte(),
(this.ushr(0) and 0xFF).toByte()
)
/**
* 255u -> 00 00 00 FF
*/
fun UInt.toByteArray(): ByteArray = byteArrayOf(
(this.shr(24) and 255u).toByte(),
(this.shr(16) and 255u).toByte(),
(this.shr(8) and 255u).toByte(),
(this.shr(0) and 255u).toByte()
)
fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator)
fun Byte.toUHexString(): String = this.toUByte().toString(16).toUpperCase()
fun String.hexToBytes(): ByteArray = HexCache.hexToBytes(this)
fun String.hexToUBytes(): UByteArray = HexCache.hexToUBytes(this)
fun String.hexToInt(): Int = hexToBytes().toUInt().toInt()
fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0..255).toByte() }
fun ByteArray.toUInt(): UInt = this[0].toUInt().and(255u).shl(24) + this[1].toUInt().and(255u).shl(16) + this[2].toUInt().and(255u).shl(8) + this[3].toUInt().and(255u).shl(0)
fun ByteArray.toIoBuffer(): IoBuffer = IoBuffer.Pool.borrow().let { it.writeFully(this); it }
internal object HexCache {
private val hexToByteArrayCacheMap: MutableMap<Int, ByteArray> = mutableMapOf()
@Synchronized
internal fun hexToBytes(hex: String): ByteArray = hex.hashCode().let { id ->
if (hexToByteArrayCacheMap.containsKey(id)) {
return hexToByteArrayCacheMap[id]!!.copyOf()
} else {
hexToUBytes(hex).toByteArray().let {
hexToByteArrayCacheMap[id] = it.copyOf()
return it
}
}
}
internal fun hexToUBytes(hex: String): UByteArray =
hex.split(" ").dropLastWhile { it.isEmpty() }.toTypedArray()
.map { value -> value.trim { it <= ' ' } }
.map { s -> s.toUByte(16) }
.toUByteArray()
}
\ No newline at end of file
package net.mamoe.mirai.utils
//todo
\ No newline at end of file
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
import kotlinx.coroutines.withContext
import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.network.LoginSession
import net.mamoe.mirai.network.protocol.tim.packet.image.ClientTryGetImageIDPacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.image.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.network.protocol.tim.packet.md5
import net.mamoe.mirai.network.protocol.tim.packet.ClientTryGetImageIDPacketJvm
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDFailedPacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDResponsePacket
import net.mamoe.mirai.network.protocol.tim.packet.ServerTryGetImageIDSuccessPacket
import net.mamoe.mirai.qqNumber
import net.mamoe.mirai.utils.ImageNetworkUtils
import net.mamoe.mirai.utils.md5
import net.mamoe.mirai.utils.toByteArray
import net.mamoe.mirai.utils.toUHexString
import java.awt.image.BufferedImage
......@@ -32,7 +32,7 @@ class UnsolvedImage(private val filename: String, val image: BufferedImage) {
suspend fun upload(session: LoginSession, contact: Contact): CompletableDeferred<Unit> {
return session.expectPacket<ServerTryGetImageIDResponsePacket> {
toSend { ClientTryGetImageIDPacket(session.bot.qqNumber, session.sessionKey, contact.number, image) }
toSend { ClientTryGetImageIDPacketJvm(session.bot.qqNumber, session.sessionKey, contact.number, image) }
onExpect {
when (it) {
......
package net.mamoe.mirai.network
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
object NetworkScope : CoroutineScope by CoroutineScope(Dispatchers.Default)
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
/**
* @author Him188moe
*/
interface Packet
internal object PacketNameFormatter {
@JvmStatic
private var longestNameLength: Int = 43
@JvmStatic
fun adjustName(name: String): String {
if (name.length > longestNameLength) {
longestNameLength = name.length
return name
}
return " ".repeat(longestNameLength - name.length) + name
}
}
\ No newline at end of file
package net.mamoe.mirai.network.protocol.tim.packet
import net.mamoe.mirai.utils.LoggerTextFormat
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.toUHexString
import java.io.DataInputStream
/**
* @author Him188moe
*/
class UnknownServerPacket(input: DataInputStream) : ServerPacket(input) {
override fun decode() {
MiraiLogger.debug("UnknownServerPacket data: " + this.input.goto(0).readAllBytes().toUHexString())
}
override fun toString(): String {
return LoggerTextFormat.LIGHT_RED.toString() + super.toString()
}
}
\ No newline at end of file
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