mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-22 13:46:13 +08:00
Adjust running coroutineContext
This commit is contained in:
parent
ead17724b6
commit
d3270bb36f
@ -14,8 +14,7 @@ import net.mamoe.mirai.withSession
|
|||||||
class ContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
|
class ContactList<C : Contact> : MutableMap<UInt, C> by mutableMapOf()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 联系人. 虽然叫做联系人, 但它直营
|
* 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group].
|
||||||
* 现支持的联系人只有 [QQ号][QQ] 和 [群][Group].
|
|
||||||
*
|
*
|
||||||
* @param bot 这个联系人所属 [Bot]
|
* @param bot 这个联系人所属 [Bot]
|
||||||
* @param id 可以是 QQ 号码或者群号码 [GroupId].
|
* @param id 可以是 QQ 号码或者群号码 [GroupId].
|
||||||
@ -70,8 +69,7 @@ inline class GroupInternalId(val value: UInt)
|
|||||||
class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot, groupId.value) {
|
class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot, groupId.value) {
|
||||||
val internalId = GroupId(id).toInternalId()
|
val internalId = GroupId(id).toInternalId()
|
||||||
val members: ContactList<QQ>
|
val members: ContactList<QQ>
|
||||||
//todo members
|
get() = TODO("Implementing group members is less important")
|
||||||
get() = throw UnsupportedOperationException("Not yet supported")
|
|
||||||
|
|
||||||
override suspend fun sendMessage(message: MessageChain) {
|
override suspend fun sendMessage(message: MessageChain) {
|
||||||
bot.network[EventPacketHandler].sendGroupMessage(this, message)
|
bot.network[EventPacketHandler].sendGroupMessage(this, message)
|
||||||
@ -87,7 +85,7 @@ class Group internal constructor(bot: Bot, val groupId: GroupId) : Contact(bot,
|
|||||||
inline fun <R> Group.withSession(block: BotSession.() -> R): R = bot.withSession(block)
|
inline fun <R> Group.withSession(block: BotSession.() -> R): R = bot.withSession(block)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* QQ 账号.
|
* QQ 对象.
|
||||||
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
|
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
|
||||||
*
|
*
|
||||||
* A QQ instance helps you to receive event from or sendPacket event to.
|
* A QQ instance helps you to receive event from or sendPacket event to.
|
||||||
@ -95,7 +93,7 @@ inline fun <R> Group.withSession(block: BotSession.() -> R): R = bot.withSession
|
|||||||
*
|
*
|
||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
class QQ internal constructor(bot: Bot, number: UInt) : Contact(bot, number) {
|
class QQ internal constructor(bot: Bot, id: UInt) : Contact(bot, id) {
|
||||||
override suspend fun sendMessage(message: MessageChain) {
|
override suspend fun sendMessage(message: MessageChain) {
|
||||||
bot.network[EventPacketHandler].sendFriendMessage(this, message)
|
bot.network[EventPacketHandler].sendFriendMessage(this, message)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package net.mamoe.mirai.network
|
|||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.cancelChildren
|
import kotlinx.coroutines.cancelChildren
|
||||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter
|
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.BotSocketAdapter
|
||||||
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
|
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler.LoginHandler
|
||||||
@ -15,7 +16,6 @@ import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult
|
|||||||
import net.mamoe.mirai.network.protocol.tim.packet.login.RequestSKeyPacket
|
import net.mamoe.mirai.network.protocol.tim.packet.login.RequestSKeyPacket
|
||||||
import net.mamoe.mirai.utils.BotNetworkConfiguration
|
import net.mamoe.mirai.utils.BotNetworkConfiguration
|
||||||
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
|
import net.mamoe.mirai.utils.io.PlatformDatagramChannel
|
||||||
import kotlin.coroutines.ContinuationInterceptor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
|
* Mirai 的网络处理器, 它承担所有数据包([Packet])的处理任务.
|
||||||
@ -86,7 +86,8 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
|
|||||||
* 关闭网络接口, 停止所有有关协程和任务
|
* 关闭网络接口, 停止所有有关协程和任务
|
||||||
*/
|
*/
|
||||||
suspend fun close(cause: Throwable? = null) {
|
suspend fun close(cause: Throwable? = null) {
|
||||||
//todo check??
|
val job = coroutineContext[Job]
|
||||||
coroutineContext[ContinuationInterceptor]!!.cancelChildren(CancellationException("handler closed", cause))
|
checkNotNull(job) { "Job should not be null because there will always be a SupervisorJob. There may be a internal mistake" }
|
||||||
|
job.cancelChildren(CancellationException("handler closed", cause))
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -33,7 +33,7 @@ internal inline fun TIMBotNetworkHandler.BotSession(
|
|||||||
*/
|
*/
|
||||||
class BotSession(
|
class BotSession(
|
||||||
val bot: Bot,
|
val bot: Bot,
|
||||||
val sessionKey: ByteArray,//TODO 协议抽象? 可能并不是所有协议均需要 sessionKey
|
val sessionKey: ByteArray,
|
||||||
val socket: DataPacketSocketAdapter,
|
val socket: DataPacketSocketAdapter,
|
||||||
val NetworkScope: CoroutineScope
|
val NetworkScope: CoroutineScope
|
||||||
) {
|
) {
|
||||||
@ -61,7 +61,7 @@ class BotSession(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P].
|
* 发送一个数据包, 并期待接受一个特定的 [ServerPacket][P].
|
||||||
* 发送成功后, 该方法会等待收到 [ServerPacket][P] 直到超时.
|
* 这个方法会立即返回.
|
||||||
*
|
*
|
||||||
* 实现方法:
|
* 实现方法:
|
||||||
* ```kotlin
|
* ```kotlin
|
||||||
@ -75,13 +75,13 @@ class BotSession(
|
|||||||
*
|
*
|
||||||
* @param checkSequence 是否期待 [ServerPacket.sequenceId] 与 [OutgoingPacket.sequenceId] 相同的包.
|
* @param checkSequence 是否期待 [ServerPacket.sequenceId] 与 [OutgoingPacket.sequenceId] 相同的包.
|
||||||
* @param P 期待的包
|
* @param P 期待的包
|
||||||
* @param handler 处理期待的包
|
* @param handler 处理期待的包. 将会在调用 [sendAndExpect] 的函数所在 [coroutineContext] 下执行.
|
||||||
*
|
*
|
||||||
* @see Bot.withSession 转换接收器 (receiver, 即 `this` 的指向) 为 [BotSession]
|
* @see Bot.withSession 转换接收器 (receiver, 即 `this` 的指向) 为 [BotSession]
|
||||||
*/
|
*/
|
||||||
suspend inline fun <reified P : ServerPacket, R> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true, noinline handler: suspend (P) -> R): CompletableDeferred<R> {
|
suspend inline fun <reified P : ServerPacket, R> OutgoingPacket.sendAndExpect(checkSequence: Boolean = true, noinline handler: suspend (P) -> R): CompletableDeferred<R> {
|
||||||
val deferred: CompletableDeferred<R> = CompletableDeferred(coroutineContext[Job])
|
val deferred: CompletableDeferred<R> = CompletableDeferred(coroutineContext[Job])
|
||||||
bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSession, checkSequence).also {
|
bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSession, checkSequence, coroutineContext + deferred).also {
|
||||||
it.toSend(this)
|
it.toSend(this)
|
||||||
it.onExpect(handler)
|
it.onExpect(handler)
|
||||||
})
|
})
|
||||||
|
@ -36,7 +36,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, PacketHandlerList() {
|
BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, PacketHandlerList() {
|
||||||
|
|
||||||
override val coroutineContext: CoroutineContext =
|
override val coroutineContext: CoroutineContext =
|
||||||
Dispatchers.Default + CoroutineExceptionHandler { _, e -> bot.logger.log(e) }
|
Dispatchers.Default + CoroutineExceptionHandler { _, e -> bot.logger.log(e) } + SupervisorJob()
|
||||||
|
|
||||||
|
|
||||||
override lateinit var socket: BotSocketAdapter
|
override lateinit var socket: BotSocketAdapter
|
||||||
private set
|
private set
|
||||||
@ -156,7 +157,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal suspend fun resendTouch(): LoginResult {
|
internal suspend fun resendTouch(): LoginResult = coroutineScope {
|
||||||
if (::loginHandler.isInitialized) loginHandler.close()
|
if (::loginHandler.isInitialized) loginHandler.close()
|
||||||
|
|
||||||
loginHandler = LoginHandler(configuration)
|
loginHandler = LoginHandler(configuration)
|
||||||
@ -169,9 +170,9 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
loginResult.complete(LoginResult.TIMEOUT)
|
loginResult.complete(LoginResult.TIMEOUT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sendPacket(TouchPacket(bot.qqAccount, this.serverIp))
|
sendPacket(TouchPacket(bot.qqAccount, serverIp))
|
||||||
|
|
||||||
return loginResult.await()
|
return@coroutineScope loginResult.await()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend inline fun <reified P : ServerPacket> expectPacket(): CompletableDeferred<P> {
|
private suspend inline fun <reified P : ServerPacket> expectPacket(): CompletableDeferred<P> {
|
||||||
@ -199,8 +200,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
packet.use {
|
packet.use {
|
||||||
val name = packet::class.simpleName
|
packet::class.simpleName?.takeIf { !it.endsWith("Encrypted") && !it.endsWith("Raw") }?.let {
|
||||||
if (name != null && !name.endsWith("Encrypted") && !name.endsWith("Raw")) {
|
|
||||||
bot.logger.logCyan("Packet received: $packet")
|
bot.logger.logCyan("Packet received: $packet")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -208,11 +208,11 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
handlersLock.withLock {
|
handlersLock.withLock {
|
||||||
temporaryPacketHandlers.filter { it.filter(session, packet) }
|
temporaryPacketHandlers.filter { it.filter(session, packet) }
|
||||||
}.forEach {
|
}.forEach {
|
||||||
it.doReceive(packet)
|
it.doReceiveWithoutExceptions(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet is ServerEventPacket) {
|
if (packet is ServerEventPacket) {
|
||||||
//ensure the response packet is sent
|
// Ensure the response packet is sent
|
||||||
sendPacket(packet.ResponsePacket(bot.qqAccount, sessionKey))
|
sendPacket(packet.ResponsePacket(bot.qqAccount, sessionKey))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,7 +220,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
return@coroutineScope
|
return@coroutineScope
|
||||||
}
|
}
|
||||||
|
|
||||||
//they should be called in sequence otherwise because packet is lock-free
|
// They should be called in sequence otherwise because packet is lock-free
|
||||||
loginHandler.onPacketReceived(packet)
|
loginHandler.onPacketReceived(packet)
|
||||||
this@TIMBotNetworkHandler.forEach {
|
this@TIMBotNetworkHandler.forEach {
|
||||||
it.instance.onPacketReceived(packet)
|
it.instance.onPacketReceived(packet)
|
||||||
@ -293,9 +293,6 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
private lateinit var loginIP: String
|
private lateinit var loginIP: String
|
||||||
private var privateKey: ByteArray = getRandomByteArray(16)
|
private var privateKey: ByteArray = getRandomByteArray(16)
|
||||||
|
|
||||||
/**
|
|
||||||
* 0828_decr_key
|
|
||||||
*/
|
|
||||||
private lateinit var sessionResponseDecryptionKey: IoBuffer
|
private lateinit var sessionResponseDecryptionKey: IoBuffer
|
||||||
|
|
||||||
private var captchaSectionId: Int = 1
|
private var captchaSectionId: Int = 1
|
||||||
@ -406,7 +403,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
|
|||||||
bot.qqAccount,
|
bot.qqAccount,
|
||||||
token0825,
|
token0825,
|
||||||
code,
|
code,
|
||||||
packet.verificationToken
|
packet.captchaToken
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,13 @@
|
|||||||
|
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||||
|
|
||||||
package net.mamoe.mirai.network.protocol.tim.handler
|
package net.mamoe.mirai.network.protocol.tim.handler
|
||||||
|
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import net.mamoe.mirai.network.BotSession
|
import net.mamoe.mirai.network.BotSession
|
||||||
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
|
import net.mamoe.mirai.network.protocol.tim.packet.OutgoingPacket
|
||||||
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
|
import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -23,7 +27,11 @@ class TemporaryPacketHandler<P : ServerPacket, R>(
|
|||||||
private val expectationClass: KClass<P>,
|
private val expectationClass: KClass<P>,
|
||||||
private val deferred: CompletableDeferred<R>,
|
private val deferred: CompletableDeferred<R>,
|
||||||
private val fromSession: BotSession,
|
private val fromSession: BotSession,
|
||||||
private val checkSequence: Boolean
|
private val checkSequence: Boolean,
|
||||||
|
/**
|
||||||
|
* 调用者的 [CoroutineContext]
|
||||||
|
*/
|
||||||
|
private val callerContext: CoroutineContext
|
||||||
) {
|
) {
|
||||||
private lateinit var toSend: OutgoingPacket
|
private lateinit var toSend: OutgoingPacket
|
||||||
|
|
||||||
@ -40,21 +48,22 @@ class TemporaryPacketHandler<P : ServerPacket, R>(
|
|||||||
this.handler = handler
|
this.handler = handler
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun send(session: BotSession) {
|
internal suspend fun send(session: BotSession) {
|
||||||
require(::handler.isInitialized) { "handler is not initialized" }
|
require(::handler.isInitialized) { "handler is not initialized" }
|
||||||
this.session = session
|
this.session = session
|
||||||
session.socket.sendPacket(toSend)
|
session.socket.sendPacket(toSend)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExperimentalUnsignedTypes
|
internal fun filter(session: BotSession, packet: ServerPacket): Boolean =
|
||||||
fun filter(session: BotSession, packet: ServerPacket): Boolean =
|
|
||||||
expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) packet.sequenceId == toSend.sequenceId else true
|
expectationClass.isInstance(packet) && session === this.fromSession && if (checkSequence) packet.sequenceId == toSend.sequenceId else true
|
||||||
|
|
||||||
suspend fun doReceive(packet: ServerPacket) {
|
internal suspend fun doReceiveWithoutExceptions(packet: ServerPacket) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
val ret = try {
|
val ret = try {
|
||||||
|
withContext(callerContext) {
|
||||||
handler(packet as P)
|
handler(packet as P)
|
||||||
} catch (e: Exception) {
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
deferred.completeExceptionally(e)
|
deferred.completeExceptionally(e)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ import net.mamoe.mirai.utils.io.*
|
|||||||
class RequestCaptchaTransmissionPacket(
|
class RequestCaptchaTransmissionPacket(
|
||||||
private val bot: UInt,
|
private val bot: UInt,
|
||||||
private val token0825: ByteArray,
|
private val token0825: ByteArray,
|
||||||
private val verificationSequence: Int,
|
private val captchaSequence: Int,
|
||||||
private val token00BA: ByteArray
|
private val token00BA: ByteArray
|
||||||
) : OutgoingPacket() {
|
) : OutgoingPacket() {
|
||||||
@Tested
|
@Tested
|
||||||
@ -31,7 +31,7 @@ class RequestCaptchaTransmissionPacket(
|
|||||||
writeHex("01 03 00 19")
|
writeHex("01 03 00 19")
|
||||||
writeHex(TIMProtocol.publicKey)
|
writeHex(TIMProtocol.publicKey)
|
||||||
writeHex("13 00 05 00 00 00 00")
|
writeHex("13 00 05 00 00 00 00")
|
||||||
writeUByte(verificationSequence.toUByte())
|
writeUByte(captchaSequence.toUByte())
|
||||||
writeHex("00 28")
|
writeHex("00 28")
|
||||||
writeFully(token00BA)
|
writeFully(token00BA)
|
||||||
writeHex("00 10")
|
writeHex("00 10")
|
||||||
@ -48,7 +48,7 @@ class SubmitCaptchaPacket(
|
|||||||
private val bot: UInt,
|
private val bot: UInt,
|
||||||
private val token0825: ByteArray,
|
private val token0825: ByteArray,
|
||||||
private val captcha: String,
|
private val captcha: String,
|
||||||
private val verificationToken: IoBuffer
|
private val captchaToken: IoBuffer
|
||||||
) : OutgoingPacket() {
|
) : OutgoingPacket() {
|
||||||
init {
|
init {
|
||||||
require(captcha.length == 4) { "captcha.length must == 4" }
|
require(captcha.length == 4) { "captcha.length must == 4" }
|
||||||
@ -71,7 +71,7 @@ class SubmitCaptchaPacket(
|
|||||||
writeHex("14 00 05 00 00 00 00 00 04")
|
writeHex("14 00 05 00 00 00 00 00 04")
|
||||||
writeStringUtf8(captcha.toUpperCase())
|
writeStringUtf8(captcha.toUpperCase())
|
||||||
writeHex("00 38")
|
writeHex("00 38")
|
||||||
writeFully(verificationToken)
|
writeFully(captchaToken)
|
||||||
|
|
||||||
writeShort(16)
|
writeShort(16)
|
||||||
writeHex(TIMProtocol.key00BAFix)//16
|
writeHex(TIMProtocol.key00BAFix)//16
|
||||||
@ -113,14 +113,14 @@ class OutgoingCaptchaRefreshPacket(
|
|||||||
open class CaptchaTransmissionResponsePacket(input: ByteReadPacket) : ServerCaptchaPacket(input) {
|
open class CaptchaTransmissionResponsePacket(input: ByteReadPacket) : ServerCaptchaPacket(input) {
|
||||||
|
|
||||||
lateinit var captchaSectionN: IoBuffer
|
lateinit var captchaSectionN: IoBuffer
|
||||||
lateinit var verificationToken: IoBuffer//56bytes
|
lateinit var captchaToken: IoBuffer//56bytes
|
||||||
var transmissionCompleted: Boolean = false//验证码是否已经传输完成
|
var transmissionCompleted: Boolean = false//验证码是否已经传输完成
|
||||||
lateinit var token00BA: ByteArray//40 bytes
|
lateinit var token00BA: ByteArray//40 bytes
|
||||||
|
|
||||||
|
|
||||||
override fun decode() = with(input) {
|
override fun decode() = with(input) {
|
||||||
input.discardExact(10)//13 00 05 01 00 00 01 23 00 38
|
input.discardExact(10)//13 00 05 01 00 00 01 23 00 38
|
||||||
verificationToken = readIoBuffer(56)
|
captchaToken = readIoBuffer(56)
|
||||||
|
|
||||||
val length = readShort()
|
val length = readShort()
|
||||||
captchaSectionN = readIoBuffer(length)
|
captchaSectionN = readIoBuffer(length)
|
||||||
|
@ -5,4 +5,5 @@ package net.mamoe.mirai.utils
|
|||||||
*
|
*
|
||||||
* @author Him188moe
|
* @author Him188moe
|
||||||
*/
|
*/
|
||||||
internal annotation class Tested
|
@Suppress("unused")
|
||||||
|
internal annotation class Tested(val date: String = "")
|
Loading…
Reference in New Issue
Block a user