1
0
mirror of https://github.com/mamoe/mirai.git synced 2025-04-09 02:10:10 +08:00

Let BotNetworkHandler implements CoroutineScope

This commit is contained in:
Him188 2019-10-24 10:59:13 +08:00
parent b7628cd3d6
commit eed9c8f160
27 changed files with 530 additions and 370 deletions

View File

@ -2,12 +2,12 @@ buildscript {
ext.kotlin_version = '1.3.50'
repositories {
google()
mavenLocal()
jcenter()
mavenCentral()
maven { url "https://mirrors.huaweicloud.com/repository/maven/" }
}
apply from: rootProject.file('dependencies.gradle')
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
@ -16,14 +16,12 @@ buildscript {
allprojects {
group = "net.mamoe"
version = "1.0"
version = getProperty("mirai_version")
repositories {
jcenter()//klock
google()
mavenLocal()
mavenCentral()
jcenter()
maven { url "https://mirrors.huaweicloud.com/repository/maven/" }
}
apply from: rootProject.file('dependencies.gradle')
}
}

View File

@ -1,44 +0,0 @@
ext {
// dependencies
// 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"
// kotlinx.coroutine
coroutine_version = "1.3.0"
coroutine = "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version"
coroutineCommon = "org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutine_version"
coroutineNative = "org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutine_version"
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"
// kotlin.reflect
reflect = "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
// kotlinx.atomicfu
atomicfu_version = "0.13.1"
atomicFUCommon = "org.jetbrains.kotlinx:atomicfu-common:$atomicfu_version"
// kotlinx.io
kotlinx_io_version = "0.1.15"
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"
// klock
klock = "com.soywiz.korlibs.klock:klock:1.7.0"
// ktor
ktor_version = "1.2.4"
ktorClientCore = "io.ktor:ktor-client-core:$ktor_version"
ktorClientCoreNative = "io.ktor:ktor-client-core-native:$ktor_version"
ktorClientCoreJvm = "io.ktor:ktor-client-core-jvm:$ktor_version"
ktorClientCio = "io.ktor:ktor-client-cio:$ktor_version"
ktorHttp = "io.ktor:ktor-http:$ktor_version"
ktorHttpCio = "io.ktor:ktor-http-cio:$ktor_version"
}

18
gradle.properties Normal file
View File

@ -0,0 +1,18 @@
# style guide
kotlin.code.style=official
# config
mirai_version=1.0.0
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
# kotlin
kotlin_version=1.3.50
# kotlin libraries
serialization_version=0.13.0
coroutines_version=1.3.2
atomicfu_version=0.13.0
ktor_version=1.2.4
ktorio_version=1.3.0-beta-1
klock_version=1.7.0
kotlinxio_version=0.1.15
coroutinesio_version=0.24.0
# utility

View File

@ -1,18 +1,6 @@
apply plugin: "kotlin"
apply plugin: "application"
apply plugin: "java"
dependencies {
compile project(':mirai-core')
compile rootProject.ext.kotlinCommon
compile rootProject.ext.kotlinJvm
compile rootProject.ext.reflect
compile rootProject.ext.coroutine
}
sourceCompatibility = "11"
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
implementation project(':mirai-core')
}

View File

@ -23,24 +23,21 @@ kotlin {
sourceSets {
commonMain {
dependencies {
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-reflect
implementation rootProject.ext.kotlinCommon
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-common', version: kotlin_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core-common', version: coroutines_version
api group: 'org.jetbrains.kotlinx', name: 'atomicfu-common', version: atomicfu_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-io', version: coroutinesio_version
implementation rootProject.ext.reflect
implementation "com.soywiz.korlibs.klock:klock:$klock_version"
//implementation rootProject.ext.coroutine
implementation rootProject.ext.coroutineCommon
implementation rootProject.ext.coroutineIo
implementation rootProject.ext.atomicFUCommon
implementation rootProject.ext.kotlinxIOCommon
implementation rootProject.ext.klock
implementation rootProject.ext.ktorClientCore
implementation rootProject.ext.ktorClientCio
implementation rootProject.ext.ktorHttp
implementation rootProject.ext.ktorHttpCio
api group: 'io.ktor', name: 'ktor-client-core', version: ktor_version
//api group: 'io.ktor', name: 'ktor-client-cio', version: ktor_version
//api group: 'io.ktor', name: 'ktor-client', version: ktor_version
api group: 'io.ktor', name: 'ktor-http', version: ktor_version
//api group: 'io.ktor', name: 'ktor-utils', version: ktor_version
//api group: 'io.ktor', name: 'ktor-io', version: ktorio_version
}
}
@ -48,22 +45,23 @@ kotlin {
apply plugin: 'java'
dependencies {
implementation rootProject.ext.kotlinJvm
implementation rootProject.ext.reflect
implementation rootProject.ext.coroutine
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib', version: kotlin_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
api group: 'org.jetbrains.kotlinx', name: 'atomicfu', version: atomicfu_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version
// api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io-jvm', version: kotlinxio_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-io', version: coroutinesio_version
implementation rootProject.ext.kotlinxIOJvm
api group: 'io.ktor', name: 'ktor-http-cio', version: ktor_version
api group: 'io.ktor', name: 'ktor-http', version: ktor_version
api group: 'io.ktor', name: 'ktor-client-core-jvm', version: ktor_version
api group: 'io.ktor', name: 'ktor-client-cio', version: ktor_version
implementation 'org.yaml:snakeyaml:1.18'
implementation 'org.jsoup:jsoup:1.12.1'
implementation 'org.ini4j:ini4j:0.5.2'
implementation rootProject.ext.klock
implementation rootProject.ext.ktorClientCore
implementation rootProject.ext.ktorClientCoreJvm
implementation rootProject.ext.ktorClientCio
implementation rootProject.ext.ktorHttp
implementation rootProject.ext.ktorHttpCio
}
}

View File

@ -110,7 +110,7 @@ class Bot(val account: BotAccount, val logger: MiraiLogger) {
}
}
fun close() {
suspend fun close() {
this.network.close()
this.contacts.groups.clear()
this.contacts.qqs.clear()

View File

@ -4,13 +4,14 @@ package net.mamoe.mirai.event
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.newCoroutineContext
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.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmOverloads
/**
@ -57,17 +58,8 @@ interface Cancellable {
*/
@Suppress("UNCHECKED_CAST")
@JvmOverloads
suspend fun <E : Event> E.broadcast(context: CoroutineContext? = CoroutineExceptionHandler { _, e -> e.log() }): E {
var ctx = EventScope.coroutineContext
if (context == null) {
ctx += CoroutineExceptionHandler { _, e -> e.log() }
} else {
ctx += context
if (context[CoroutineExceptionHandler] == null) {
ctx += CoroutineExceptionHandler { _, e -> e.log() }
}
}
return withContext(ctx) { this@broadcast.broadcastInternal() }
suspend fun <E : Event> E.broadcast(context: CoroutineContext = EmptyCoroutineContext): E {
return withContext(EventScope.newCoroutineContext(context)) { this@broadcast.broadcastInternal() }
}
/**
@ -77,4 +69,6 @@ suspend fun <E : Event> E.broadcast(context: CoroutineContext? = CoroutineExcept
* 然而, 若在事件处理过程中使用到 [Contact.sendMessage] 等会 [发送数据包][BotNetworkHandler.sendPacket] 的方法,
* 发送过程将会通过 [withContext] 将协程切换到 [BotNetworkHandler.NetworkScope]
*/
object EventScope : CoroutineScope by CoroutineScope(Dispatchers.Default)//todo may change
object EventScope : CoroutineScope {
override val coroutineContext: CoroutineContext = EmptyCoroutineContext
}

View File

@ -21,44 +21,72 @@ enum class ListeningStatus {
// region 顶层方法
inline fun <reified E : Event> subscribe(noinline handler: suspend (E) -> ListeningStatus) = E::class.subscribe(handler)
suspend inline fun <reified E : Event> subscribe(noinline handler: suspend (E) -> ListeningStatus) =
E::class.subscribe(handler)
inline fun <reified E : Event> subscribeAlways(noinline listener: suspend (E) -> Unit) = E::class.subscribeAlways(listener)
suspend inline fun <reified E : Event> subscribeAlways(noinline listener: suspend (E) -> Unit) =
E::class.subscribeAlways(listener)
inline fun <reified E : Event> subscribeOnce(noinline listener: suspend (E) -> Unit) = E::class.subscribeOnce(listener)
suspend inline fun <reified E : Event> subscribeOnce(noinline listener: suspend (E) -> Unit) =
E::class.subscribeOnce(listener)
inline fun <reified E : Event, T> subscribeUntil(valueIfStop: T, noinline listener: suspend (E) -> T) = E::class.subscribeUntil(valueIfStop, listener)
inline fun <reified E : Event> subscribeUntilFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilFalse(listener)
inline fun <reified E : Event> subscribeUntilTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeUntilTrue(listener)
inline fun <reified E : Event> subscribeUntilNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeUntilNull(listener)
suspend inline fun <reified E : Event, T> subscribeUntil(valueIfStop: T, noinline listener: suspend (E) -> T) =
E::class.subscribeUntil(valueIfStop, listener)
suspend inline fun <reified E : Event> subscribeUntilFalse(noinline listener: suspend (E) -> Boolean) =
E::class.subscribeUntilFalse(listener)
suspend inline fun <reified E : Event> subscribeUntilTrue(noinline listener: suspend (E) -> Boolean) =
E::class.subscribeUntilTrue(listener)
suspend inline fun <reified E : Event> subscribeUntilNull(noinline listener: suspend (E) -> Any?) =
E::class.subscribeUntilNull(listener)
inline fun <reified E : Event, T> subscribeWhile(valueIfContinue: T, noinline listener: suspend (E) -> T) = E::class.subscribeWhile(valueIfContinue, listener)
inline fun <reified E : Event> subscribeWhileFalse(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileFalse(listener)
inline fun <reified E : Event> subscribeWhileTrue(noinline listener: suspend (E) -> Boolean) = E::class.subscribeWhileTrue(listener)
inline fun <reified E : Event> subscribeWhileNull(noinline listener: suspend (E) -> Any?) = E::class.subscribeWhileNull(listener)
suspend inline fun <reified E : Event, T> subscribeWhile(valueIfContinue: T, noinline listener: suspend (E) -> T) =
E::class.subscribeWhile(valueIfContinue, listener)
suspend inline fun <reified E : Event> subscribeWhileFalse(noinline listener: suspend (E) -> Boolean) =
E::class.subscribeWhileFalse(listener)
suspend inline fun <reified E : Event> subscribeWhileTrue(noinline listener: suspend (E) -> Boolean) =
E::class.subscribeWhileTrue(listener)
suspend inline fun <reified E : Event> subscribeWhileNull(noinline listener: suspend (E) -> Any?) =
E::class.subscribeWhileNull(listener)
// endregion
// region KClass 的扩展方法 (不推荐)
fun <E : Event> KClass<E>.subscribe(handler: suspend (E) -> ListeningStatus) = this.subscribeInternal(Handler(handler))
suspend fun <E : Event> KClass<E>.subscribe(handler: suspend (E) -> ListeningStatus) =
this.subscribeInternal(Handler(handler))
fun <E : Event> KClass<E>.subscribeAlways(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.LISTENING })
suspend fun <E : Event> KClass<E>.subscribeAlways(listener: suspend (E) -> Unit) =
this.subscribeInternal(Handler { listener(it); ListeningStatus.LISTENING })
fun <E : Event> KClass<E>.subscribeOnce(listener: suspend (E) -> Unit) = this.subscribeInternal(Handler { listener(it); ListeningStatus.STOPPED })
suspend fun <E : Event> KClass<E>.subscribeOnce(listener: suspend (E) -> Unit) =
this.subscribeInternal(Handler { listener(it); ListeningStatus.STOPPED })
fun <E : Event, T> KClass<E>.subscribeUntil(valueIfStop: T, listener: suspend (E) -> T) = subscribeInternal(Handler { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
fun <E : Event> KClass<E>.subscribeUntilFalse(listener: suspend (E) -> Boolean) = subscribeUntil(false, listener)
fun <E : Event> KClass<E>.subscribeUntilTrue(listener: suspend (E) -> Boolean) = subscribeUntil(true, listener)
fun <E : Event> KClass<E>.subscribeUntilNull(listener: suspend (E) -> Any?) = subscribeUntil(null, listener)
suspend fun <E : Event, T> KClass<E>.subscribeUntil(valueIfStop: T, listener: suspend (E) -> T) =
subscribeInternal(Handler { if (listener(it) === valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
suspend fun <E : Event> KClass<E>.subscribeUntilFalse(listener: suspend (E) -> Boolean) =
subscribeUntil(false, listener)
suspend fun <E : Event> KClass<E>.subscribeUntilTrue(listener: suspend (E) -> Boolean) = subscribeUntil(true, listener)
suspend fun <E : Event> KClass<E>.subscribeUntilNull(listener: suspend (E) -> Any?) = subscribeUntil(null, listener)
fun <E : Event, T> KClass<E>.subscribeWhile(valueIfContinue: T, listener: suspend (E) -> T) = subscribeInternal(Handler { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
fun <E : Event> KClass<E>.subscribeWhileFalse(listener: suspend (E) -> Boolean) = subscribeWhile(false, listener)
fun <E : Event> KClass<E>.subscribeWhileTrue(listener: suspend (E) -> Boolean) = subscribeWhile(true, listener)
fun <E : Event> KClass<E>.subscribeWhileNull(listener: suspend (E) -> Any?) = subscribeWhile(null, listener)
suspend fun <E : Event, T> KClass<E>.subscribeWhile(valueIfContinue: T, listener: suspend (E) -> T) =
subscribeInternal(Handler { if (listener(it) !== valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING })
suspend fun <E : Event> KClass<E>.subscribeWhileFalse(listener: suspend (E) -> Boolean) =
subscribeWhile(false, listener)
suspend fun <E : Event> KClass<E>.subscribeWhileTrue(listener: suspend (E) -> Boolean) = subscribeWhile(true, listener)
suspend fun <E : Event> KClass<E>.subscribeWhileNull(listener: suspend (E) -> Any?) = subscribeWhile(null, listener)
// endregion
@ -68,15 +96,18 @@ fun <E : Event> KClass<E>.subscribeWhileNull(listener: suspend (E) -> Any?) = su
* 监听一个事件. 可同时进行多种方式的监听
* @see ListenerBuilder
*/
fun <E : Event> KClass<E>.subscribeAll(listeners: ListenerBuilder<E>.() -> Unit) {
ListenerBuilder<E> { this.subscribeInternal(it) }.apply(listeners)
suspend fun <E : Event> KClass<E>.subscribeAll(listeners: suspend ListenerBuilder<E>.() -> Unit) {
with(ListenerBuilder<E> { this.subscribeInternal(it) }) {
listeners()
}
}
/**
* 监听一个事件. 可同时进行多种方式的监听
* @see ListenerBuilder
*/
inline fun <reified E : Event> subscribeAll(noinline listeners: ListenerBuilder<E>.() -> Unit) = E::class.subscribeAll(listeners)
suspend inline fun <reified E : Event> subscribeAll(noinline listeners: suspend ListenerBuilder<E>.() -> Unit) =
E::class.subscribeAll(listeners)
/**
* 监听构建器. 可同时进行多种方式的监听
@ -96,27 +127,31 @@ inline fun <reified E : Event> subscribeAll(noinline listeners: ListenerBuilder<
*/
@Suppress("MemberVisibilityCanBePrivate", "unused")
inline class ListenerBuilder<out E : Event>(
private val handlerConsumer: (Handler<in E>) -> Unit
private val handlerConsumer: suspend (Handler<in E>) -> Unit
) {
fun handler(listener: suspend (E) -> ListeningStatus) {
suspend fun handler(listener: suspend (E) -> ListeningStatus) {
handlerConsumer(Handler(listener))
}
fun always(listener: suspend (E) -> Unit) = handler { listener(it); ListeningStatus.LISTENING }
suspend fun always(listener: suspend (E) -> Unit) = handler { listener(it); ListeningStatus.LISTENING }
fun <T> until(until: T, listener: suspend (E) -> T) = handler { if (listener(it) === until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
fun untilFalse(listener: suspend (E) -> Boolean) = until(false, listener)
fun untilTrue(listener: suspend (E) -> Boolean) = until(true, listener)
fun untilNull(listener: suspend (E) -> Any?) = until(null, listener)
suspend fun <T> until(until: T, listener: suspend (E) -> T) =
handler { if (listener(it) === until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
suspend fun untilFalse(listener: suspend (E) -> Boolean) = until(false, listener)
suspend fun untilTrue(listener: suspend (E) -> Boolean) = until(true, listener)
suspend fun untilNull(listener: suspend (E) -> Any?) = until(null, listener)
fun <T> `while`(until: T, listener: suspend (E) -> T) = handler { if (listener(it) !== until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
fun whileFalse(listener: suspend (E) -> Boolean) = `while`(false, listener)
fun whileTrue(listener: suspend (E) -> Boolean) = `while`(true, listener)
fun whileNull(listener: suspend (E) -> Any?) = `while`(null, listener)
suspend fun <T> `while`(until: T, listener: suspend (E) -> T) =
handler { if (listener(it) !== until) ListeningStatus.STOPPED else ListeningStatus.LISTENING }
suspend fun whileFalse(listener: suspend (E) -> Boolean) = `while`(false, listener)
suspend fun whileTrue(listener: suspend (E) -> Boolean) = `while`(true, listener)
suspend fun whileNull(listener: suspend (E) -> Any?) = `while`(null, listener)
fun once(block: suspend (E) -> Unit) = handler { block(it); ListeningStatus.STOPPED }
suspend fun once(block: suspend (E) -> Unit) = handler { block(it); ListeningStatus.STOPPED }
}
// endregion

View File

@ -1,18 +1,48 @@
package net.mamoe.mirai.event.internal
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.EventScope
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.utils.inlinedRemoveIf
import kotlin.reflect.KClass
/**
* 监听和广播实现
* 监听和广播实现.
* 它会首先检查这个事件是否正在被广播
* - 如果是, 则将新的监听者放入缓存中. 在当前广播结束后转移到主列表 (通过一个协程完成)
* - 如果不是, 则直接将新的监听者放入主列表
*
* @author Him188moe
*/
internal fun <E : Event> KClass<E>.subscribeInternal(listener: Listener<E>) = this.listeners.add(listener)//TODO lock or sth else
internal suspend fun <E : Event> KClass<E>.subscribeInternal(listener: Listener<E>): Unit = with(this.listeners()) {
if (mainMutex.tryLock()) {//能锁则代表这个事件目前没有正在广播.
try {
add(listener)//直接修改主监听者列表
} finally {
mainMutex.unlock()
}
return
}
//不能锁住, 则这个事件正在广播, 那么要将新的监听者放入缓存
cacheMutex.withLock {
cache.add(listener)
}
EventScope.launch {
//启动协程并等待正在进行的广播结束, 然后将缓存转移到主监听者列表
//启动后的协程马上就会因为锁而被挂起
mainMutex.withLock {
if (cache.size != 0) {
addAll(cache)
cache.clear()
}
}
}
}
/**
* 事件监听器
@ -31,18 +61,37 @@ class Handler<E : Event>(val handler: suspend (E) -> ListeningStatus) : Listener
override suspend fun onEvent(event: E): ListeningStatus = handler.invoke(event)
}
internal val <E : Event> KClass<E>.listeners: EventListeners<E> get() = EventListenerManger.get(this)
/**
* 这个事件类的监听器 list
*/
internal suspend fun <E : Event> KClass<E>.listeners(): EventListeners<E> = EventListenerManger.get(this)
internal class EventListeners<E : Event> : MutableList<Listener<E>> by mutableListOf() {
val lock = Mutex()
/**
* 主监听者列表.
* 广播事件时使用这个锁.
*/
val mainMutex = Mutex()
/**
* 缓存(监听)事件时使用的锁
*/
val cacheMutex = Mutex()
/**
* 等待加入到主 list 的监听者. 务必使用 [cacheMutex]
*/
val cache: MutableList<Listener<E>> = mutableListOf()
}
/**
* 管理每个事件 class [EventListeners].
* [EventListeners] lazy : 它们只会在被需要的时候才创建和存储.
*/
internal object EventListenerManger {
private val registries: MutableMap<KClass<out Event>, EventListeners<out Event>> = mutableMapOf()
private val registriesMutex = Mutex()
@Suppress("UNCHECKED_CAST")
internal fun <E : Event> get(clazz: KClass<E>): EventListeners<E> {
//synchronized(clazz) {
internal suspend fun <E : Event> get(clazz: KClass<E>): EventListeners<E> = registriesMutex.withLock {
if (registries.containsKey(clazz)) {
return registries[clazz] as EventListeners<E>
} else {
@ -51,21 +100,24 @@ internal object EventListenerManger {
return it
}
}
//}
}
}
@Suppress("UNCHECKED_CAST")
internal suspend fun <E : Event> E.broadcastInternal(): E {
suspend fun callListeners(listeners: EventListeners<in E>) = listeners.lock.withLock {
//fixme 这个锁会导致在事件处理时再监听这个事件死锁
//FIXME 若一个 listener 阻塞, 则这个事件全部阻塞.
suspend fun callListeners(listeners: EventListeners<in E>) = listeners.mainMutex.withLock {
listeners.inlinedRemoveIf { it.onEvent(this) == ListeningStatus.STOPPED }
}
callListeners(this::class.listeners as EventListeners<in E>)
callListeners(this::class.listeners() as EventListeners<in E>)
//FIXME 这可能不支持所有的平台. 可能需要修改.
loopAllListeners(this::class) { callListeners(it as EventListeners<in E>) }
return this
}
internal expect inline fun <E : Event> loopAllListeners(clazz: KClass<E>, consumer: (EventListeners<in E>) -> Unit)
internal expect suspend inline fun <E : Event> loopAllListeners(
clazz: KClass<E>,
consumer: (EventListeners<in E>) -> Unit
)

View File

@ -28,27 +28,25 @@ import kotlin.coroutines.ContinuationInterceptor
* - [EventPacketHandler] 处理消息相关(群消息/好友消息)([ServerEventPacket])
* - [ActionPacketHandler] 处理动作相关(踢人/加入群/好友列表等)
*
*
* NetworkHandler 实现接口 [CoroutineScope]
* [BotNetworkHandler] 自己就是作用域.
* 所有 [BotNetworkHandler] 的协程均启动在此作用域下.
*
* [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [PlatformDatagramChannel.read]
* - 心跳 Job [HeartbeatPacket]
* - SKey 刷新 [ReuestSKeyPcket]
* - 所有数据包处理和发送
*
* [BotNetworkHandler.close] 时将会 [取消][kotlin.coroutines.CoroutineContext.cancelChildren] 所有此作用域下的协程
*
* A BotNetworkHandler is used to connect with Tencent servers.
*/
@Suppress("PropertyName")
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> {
/**
* [BotNetworkHandler] 的协程作用域.
* 所有 [BotNetworkHandler] 的协程均启动在此作用域下.
*
* [BotNetworkHandler] 的协程包含:
* - UDP 包接收: [PlatformDatagramChannel.read]
* - 心跳 Job [HeartbeatPacket]
* - SKey 刷新 [RefreshSKeyRequestPacket]
* - 所有数据包处理和发送
*
* [BotNetworkHandler.close] 时将会 [取消][kotlin.coroutines.CoroutineContext.cancelChildren] 所有此作用域下的协程
*/
val NetworkScope: CoroutineScope
interface BotNetworkHandler<Socket : DataPacketSocketAdapter> : CoroutineScope {
val socket: Socket
/**
* 得到 [PacketHandler].
* `get(EventPacketHandler)` 返回 [EventPacketHandler]
@ -76,8 +74,13 @@ interface BotNetworkHandler<Socket : DataPacketSocketAdapter> {
*/
suspend fun sendPacket(packet: OutgoingPacket)
fun close(cause: Throwable? = null) {
/**
* 等待直到与服务器断开连接. 若未连接则立即返回
*/
suspend fun awaitDisconnection()
suspend fun close(cause: Throwable? = null) {
//todo check??
NetworkScope.coroutineContext[ContinuationInterceptor]!!.cancelChildren(CancellationException("handler closed", cause))
coroutineContext[ContinuationInterceptor]!!.cancelChildren(CancellationException("handler closed", cause))
}
}

View File

@ -6,6 +6,7 @@ import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot
import net.mamoe.mirai.network.protocol.tim.TIMBotNetworkHandler
import net.mamoe.mirai.network.protocol.tim.handler.ActionPacketHandler
import net.mamoe.mirai.network.protocol.tim.handler.DataPacketSocketAdapter
import net.mamoe.mirai.network.protocol.tim.handler.TemporaryPacketHandler
@ -14,6 +15,12 @@ import net.mamoe.mirai.network.protocol.tim.packet.ServerPacket
import net.mamoe.mirai.utils.getGTK
import kotlin.coroutines.coroutineContext
internal fun TIMBotNetworkHandler.BotSession(
bot: Bot,
sessionKey: ByteArray,
socket: DataPacketSocketAdapter
) = BotSession(bot, sessionKey, socket, this)
/**
* 登录会话. 当登录完成后, 客户端会拿到 sessionKey.
* 此时建立 session, 然后开始处理事务.
@ -21,10 +28,10 @@ import kotlin.coroutines.coroutineContext
* @author Him188moe
*/
class BotSession(
val bot: Bot,
val sessionKey: ByteArray,//TODO 协议抽象? 可能并不是所有协议均需要 sessionKey
val socket: DataPacketSocketAdapter,
val NetworkScope: CoroutineScope
val bot: Bot,
val sessionKey: ByteArray,//TODO 协议抽象? 可能并不是所有协议均需要 sessionKey
val socket: DataPacketSocketAdapter,
val NetworkScope: CoroutineScope
) {
/**
@ -66,7 +73,8 @@ class BotSession(
* @param handler 处理期待的包
*/
suspend inline fun <reified P : ServerPacket, R> OutgoingPacket.sendAndExpect(noinline handler: suspend (P) -> R): CompletableDeferred<R> {
val deferred: CompletableDeferred<R> = coroutineContext[Job].takeIf { it != null }?.let { CompletableDeferred<R>(it) } ?: CompletableDeferred()
val deferred: CompletableDeferred<R> =
coroutineContext[Job].takeIf { it != null }?.let { CompletableDeferred<R>(it) } ?: CompletableDeferred()
bot.network.addHandler(TemporaryPacketHandler(P::class, deferred, this@BotSession).also {
it.toSend(this)
it.onExpect(handler)
@ -74,7 +82,8 @@ class BotSession(
return deferred
}
suspend inline fun <reified P : ServerPacket> OutgoingPacket.sendAndExpect(): CompletableDeferred<Unit> = sendAndExpect<P, Unit> {}
suspend inline fun <reified P : ServerPacket> OutgoingPacket.sendAndExpect(): CompletableDeferred<Unit> =
sendAndExpect<P, Unit> {}
suspend inline fun OutgoingPacket.send() = socket.sendPacket(this)
}

View File

@ -5,7 +5,6 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.*
import net.mamoe.mirai.*
import net.mamoe.mirai.event.EventScope
import net.mamoe.mirai.event.ListeningStatus
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BeforePacketSendEvent
@ -26,14 +25,18 @@ import net.mamoe.mirai.network.session
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.parseServerPacket
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.coroutines.CoroutineContext
/**
* [BotNetworkHandler] TIM PC 协议实现
*
* @see BotNetworkHandler
*/
internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) : BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, PacketHandlerList() {
override val NetworkScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
BotNetworkHandler<TIMBotNetworkHandler.BotSocketAdapter>, PacketHandlerList() {
override val coroutineContext: CoroutineContext =
Dispatchers.Default + CoroutineExceptionHandler { _, e -> bot.logger.log(e) }
override lateinit var socket: BotSocketAdapter
private set
@ -76,7 +79,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
//private | internal
private fun onLoggedIn(sessionKey: ByteArray) {
require(size == 0) { "Already logged in" }
val session = BotSession(bot, sessionKey, socket, NetworkScope)
val session = BotSession(bot, sessionKey, socket)
add(EventPacketHandler(session).asNode(EventPacketHandler))
add(ActionPacketHandler(session).asNode(ActionPacketHandler))
@ -85,14 +88,20 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
private lateinit var sessionKey: ByteArray
override fun close(cause: Throwable?) {
override suspend fun awaitDisconnection() {
heartbeatJob?.join()
}
override suspend fun close(cause: Throwable?) {
super.close(cause)
this.heartbeatJob?.cancel(CancellationException("handler closed"))
this.heartbeatJob?.join()//等待 cancel 完成
this.heartbeatJob = null
if (!this.loginResult.isCompleted && !this.loginResult.isCancelled) {
this.loginResult.cancel(CancellationException("socket closed"))
this.loginResult.join()
}
this.forEach {
@ -104,7 +113,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
override suspend fun sendPacket(packet: OutgoingPacket) = socket.sendPacket(packet)
internal inner class BotSocketAdapter(override val serverIp: String, val configuration: BotNetworkConfiguration) : DataPacketSocketAdapter {
internal inner class BotSocketAdapter(override val serverIp: String, val configuration: BotNetworkConfiguration) :
DataPacketSocketAdapter {
override val channel: PlatformDatagramChannel = PlatformDatagramChannel(serverIp, 8000)
override val isOpen: Boolean get() = channel.isOpen
@ -130,14 +140,10 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
continue
}
NetworkScope.launch {
try {
//`.use`: Ensure that the packet is consumed totally so that all the buffers are released
ByteReadPacket(buffer, IoBuffer.Pool).use {
distributePacket(it.parseServerPacket(buffer.readRemaining))
}
} catch (e: Throwable) {
e.log()
launch {
//`.use`: Ensure that the packet is consumed totally so that all the buffers are released
ByteReadPacket(buffer, IoBuffer.Pool).use {
distributePacket(it.parseServerPacket(buffer.readRemaining))
}
}
}
@ -150,8 +156,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
val expect = expectPacket<ServerTouchResponsePacket>()
NetworkScope.launch { processReceive() }
NetworkScope.launch {
launch { processReceive() }
launch {
if (withTimeoutOrNull(configuration.touchTimeout.millisecondsLong) { expect.join() } == null) {
loginResult.complete(LoginResult.TIMEOUT)
}
@ -161,7 +167,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
return loginResult.await()
}
private inline fun <reified P : ServerPacket> expectPacket(): CompletableDeferred<P> {
private suspend inline fun <reified P : ServerPacket> expectPacket(): CompletableDeferred<P> {
val receiving = CompletableDeferred<P>()
subscribe<ServerPacketReceivedEvent> {
if (it.packet is P && it.bot === bot) {
@ -173,7 +179,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
return receiving
}
override suspend fun distributePacket(packet: ServerPacket) {
override suspend fun distributePacket(packet: ServerPacket) = coroutineScope {
try {
packet.decode()
} catch (e: Exception) {
@ -197,13 +203,13 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
if (packet is ServerEventPacket) {
//no need to sync acknowledgement packets
NetworkScope.launch {
launch {
sendPacket(packet.ResponsePacket(bot.qqAccount, sessionKey))
}
}
if (ServerPacketReceivedEvent(bot, packet).broadcast().cancelled) {
return
return@coroutineScope
}
loginHandler.onPacketReceived(packet)
@ -231,11 +237,11 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
}
}*/
override suspend fun sendPacket(packet: OutgoingPacket) = withContext(NetworkScope.coroutineContext) {
override suspend fun sendPacket(packet: OutgoingPacket): Unit = coroutineScope {
check(channel.isOpen) { "channel is not open" }
if (BeforePacketSendEvent(bot, packet).broadcast().cancelled) {
return@withContext
return@coroutineScope
}
packet.packet.use { build ->
@ -246,7 +252,7 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
} catch (e: SendPacketInternalException) {
bot.logger.logError("Caught SendPacketInternalException: ${e.cause?.message}")
bot.reinitializeNetworkHandler(configuration, e)
return@withContext
return@coroutineScope
} finally {
buffer.release(IoBuffer.Pool)
}
@ -254,7 +260,9 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
bot.logGreen("Packet sent: $packet")
EventScope.launch { PacketSentEvent(bot, packet).broadcast() }
PacketSentEvent(bot, packet).broadcast()
Unit
}
override val owner: Bot get() = this@TIMBotNetworkHandler.bot
@ -307,7 +315,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
this.loginTime = packet.loginTime
this.token0825 = packet.token0825
socket.sendPacket(OutgoingPasswordSubmissionPacket(
socket.sendPacket(
OutgoingPasswordSubmissionPacket(
bot = bot.qqAccount,
password = bot.account.password,
loginTime = loginTime,
@ -316,7 +325,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
token0825 = token0825,
token00BA = null,
randomDeviceName = socket.configuration.randomDeviceName
))
)
)
}
}
@ -329,7 +339,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
this.privateKey = getRandomByteArray(16)//似乎是必须的
this.token00BA = packet.token00BA
socket.sendPacket(OutgoingPasswordSubmissionPacket(
socket.sendPacket(
OutgoingPasswordSubmissionPacket(
bot = bot.qqAccount,
password = bot.account.password,
loginTime = loginTime,
@ -338,7 +349,8 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
token0825 = token0825,
token00BA = packet.token00BA,
randomDeviceName = socket.configuration.randomDeviceName
))
)
)
}
is ServerLoginResponseCaptchaInitPacket -> {
@ -348,7 +360,14 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
if (packet.unknownBoolean) {
this.captchaSectionId = 1
socket.sendPacket(OutgoingCaptchaTransmissionRequestPacket(bot.qqAccount, this.token0825, this.captchaSectionId++, packet.token00BA))
socket.sendPacket(
OutgoingCaptchaTransmissionRequestPacket(
bot.qqAccount,
this.token0825,
this.captchaSectionId++,
packet.token00BA
)
)
}
}
@ -372,23 +391,46 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
socket.sendPacket(OutgoingCaptchaRefreshPacket(bot.qqAccount, token0825))
} else {
this.captchaSectionId = 0//意味着已经提交验证码
socket.sendPacket(OutgoingCaptchaSubmitPacket(bot.qqAccount, token0825, code, packet.verificationToken))
socket.sendPacket(
OutgoingCaptchaSubmitPacket(
bot.qqAccount,
token0825,
code,
packet.verificationToken
)
)
}
} else {
socket.sendPacket(OutgoingCaptchaTransmissionRequestPacket(bot.qqAccount, token0825, captchaSectionId++, packet.token00BA))
socket.sendPacket(
OutgoingCaptchaTransmissionRequestPacket(
bot.qqAccount,
token0825,
captchaSectionId++,
packet.token00BA
)
)
}
}
is ServerLoginResponseSuccessPacket -> {
this.sessionResponseDecryptionKey = packet.sessionResponseDecryptionKey
socket.sendPacket(OutgoingSessionRequestPacket(bot.qqAccount, socket.serverIp, packet.token38, packet.token88, packet.encryptionKey))
socket.sendPacket(
OutgoingSessionRequestPacket(
bot.qqAccount,
socket.serverIp,
packet.token38,
packet.token88,
packet.encryptionKey
)
)
}
//是ClientPasswordSubmissionPacket之后服务器回复的可能之一
is ServerLoginResponseKeyExchangePacket -> {
this.privateKey = packet.privateKeyUpdate
socket.sendPacket(OutgoingPasswordSubmissionPacket(
socket.sendPacket(
OutgoingPasswordSubmissionPacket(
bot = bot.qqAccount,
password = bot.account.password,
loginTime = loginTime,
@ -398,22 +440,26 @@ internal class TIMBotNetworkHandler internal constructor(private val bot: Bot) :
token00BA = packet.tokenUnknown ?: token00BA,
randomDeviceName = socket.configuration.randomDeviceName,
tlv0006 = packet.tlv0006
))
)
)
}
is ServerSessionKeyResponsePacket -> {
sessionKey = packet.sessionKey
bot.logger.logPurple("sessionKey = ${sessionKey.toUHexString()}")
heartbeatJob = NetworkScope.launch {
heartbeatJob = launch {
while (socket.isOpen) {
delay(configuration.heartbeatPeriod.millisecondsLong)
with(session) {
class HeartbeatTimeoutException : CancellationException("heartbeat timeout")
if (withTimeoutOrNull(configuration.heartbeatTimeout.millisecondsLong) {
HeartbeatPacket(bot.qqAccount, sessionKey).sendAndExpect<HeartbeatPacket.Response>().join()
} == null) {
HeartbeatPacket(
bot.qqAccount,
sessionKey
).sendAndExpect<HeartbeatPacket.Response>().join()
} == null) {
bot.logPurple("Heartbeat timed out")
bot.reinitializeNetworkHandler(configuration, HeartbeatTimeoutException())
return@launch

View File

@ -2,6 +2,8 @@
package net.mamoe.mirai.network.protocol.tim.packet
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.io.core.*
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.message.ImageId
@ -21,11 +23,10 @@ suspend fun QQ.uploadImage(image: BufferedImage): ImageId = with(bot.network.ses
return FriendImageIdRequestPacket(this.qqAccount, sessionKey, this.qqAccount, image).sendAndExpect<FriendImageIdRequestPacket.Response, ImageId> {
if (it.uKey != null)
require(httpPostFriendImage(
botAccount = bot.qqAccount,
uKeyHex = it.uKey!!.toUHexString(""),
botNumber = bot.qqAccount,
imageData = image.data,
fileSize = image.fileSize,
qq = this.qqAccount
imageInput = image.input,
inputSize = image.inputSize
))
it.imageId!!
}.await()
@ -217,8 +218,10 @@ class FriendImageIdRequestPacket(
writeFully(image.md5)
writeUByte(0x28u)
writeUVarInt(image.fileSize.toUInt())
writeUVarInt(image.inputSize.toUInt())
GlobalScope.launch { }
writeUByte(0x32u)
//长度应为1A
writeUVarintLVPacket {

View File

@ -18,10 +18,10 @@ suspend fun Group.uploadImage(
.sendAndExpect<GroupImageIdRequestPacket.Response, Unit> {
if (it.uKey != null) {
httpPostGroupImage(
bot = bot.qqAccount,
botAccount = bot.qqAccount,
groupNumber = groupId,
imageData = image.data,
fileSize = image.fileSize,
imageInput = image.input,
inputSize = image.inputSize,
uKeyHex = it.uKey!!.toUHexString("")
)
}
@ -154,7 +154,7 @@ class GroupImageIdRequestPacket(
writeUByte(0x10u)
writeFully(image.md5)
writeTUVarint(0x28u, image.fileSize.toUInt())
writeTUVarint(0x28u, image.inputSize.toUInt())
writeUVarintLVPacket(tag = 0x32u) {
writeTV(0x5B_00u)
writeTV(0x40_00u)

View File

@ -3,6 +3,7 @@ package net.mamoe.mirai.utils
import com.soywiz.klock.TimeSpan
import com.soywiz.klock.seconds
import net.mamoe.mirai.network.protocol.tim.packet.login.ServerTouchResponsePacket
import kotlin.jvm.JvmField
/**
* 网络配置
@ -32,6 +33,7 @@ class BotNetworkConfiguration {
var heartbeatTimeout: TimeSpan = 2.seconds
companion object {
@JvmField
val Default = BotNetworkConfiguration()
}
}

View File

@ -1,16 +1,27 @@
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.utils
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import net.mamoe.mirai.message.ImageId
fun BufferedImage(
width: Int,
height: Int,
md5: ByteArray,
format: String,
data: ByteReadPacket
) = BufferedImage(width, height, md5, format, data, data.remaining)
class BufferedImage(
val width: Int,
val height: Int,
val md5: ByteArray,
val format: String,
val data: ByteReadPacket
val width: Int,
val height: Int,
val md5: ByteArray,
val format: String,
val input: Input,
val inputSize: Long
) {
val fileSize: Long = data.remaining
/**
* 用于发送消息的 [ImageId]

View File

@ -3,8 +3,13 @@
package net.mamoe.mirai.utils
import com.soywiz.klock.DateTime
import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.utils.io.printStringFromHex
import io.ktor.client.HttpClient
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.client.request.post
import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.userAgent
import kotlinx.io.core.Input
/**
* 时间戳
@ -39,28 +44,67 @@ expect fun solveIpAddress(hostname: String): String
*/
expect fun localIpAddress(): String
/**
* Provided by Ktor Http
*/
internal expect val httpClient: HttpClient
/**
* 上传好友图片
*/
expect suspend fun httpPostFriendImage(
uKeyHex: String,
fileSize: Long,
botNumber: UInt,
qq: UInt,
imageData: ByteReadPacket
): Boolean
@Suppress("DuplicatedCode")
suspend fun httpPostFriendImage(
botAccount: UInt,
uKeyHex: String,
imageInput: Input,
inputSize: Long
): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) {
url {
parameters["htcmd"] = "0x6ff0070"
parameters["uin"] = botAccount.toLong().toString()
}
} as HttpStatusCode).value.also { println(it) } == 200
/**
* 上传群图片
*/
expect suspend fun httpPostGroupImage(
bot: UInt,
groupNumber: UInt,
uKeyHex: String,
fileSize: Long,
imageData: ByteReadPacket
): Boolean
@Suppress("DuplicatedCode")
suspend fun httpPostGroupImage(
botAccount: UInt,
groupNumber: UInt,
uKeyHex: String,
imageInput: Input,
inputSize: Long
): Boolean = (httpClient.postImage(imageInput, inputSize, uKeyHex) {
url {
parameters["htcmd"] = "0x6ff0071"
parameters["uin"] = botAccount.toLong().toString()
parameters["groupcode"] = groupNumber.toLong().toString()
}
} as HttpStatusCode).value.also { println(it) } == 200
fun main() {
"46 52 25 46 60 30 59 4F 4A 5A 51".printStringFromHex()
}
private suspend inline fun <reified T> HttpClient.postImage(
imageInput: Input,
inputSize: Long,
uKeyHex: String,
block: HttpRequestBuilder.() -> Unit = {}
): T = post {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
parameters["ver"] = "5603"
parameters["filezise"] = inputSize.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
userAgent("QQClient")
}
block()
configureBody(inputSize, imageInput)
}
internal expect fun HttpRequestBuilder.configureBody(inputSize: Long, input: Input)

View File

@ -2,10 +2,7 @@
package net.mamoe.mirai.contact
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.Bot
import net.mamoe.mirai.message.Message
import net.mamoe.mirai.message.MessageChain
import net.mamoe.mirai.network.protocol.tim.handler.EventPacketHandler
import net.mamoe.mirai.utils.ContactList
@ -26,36 +23,36 @@ actual sealed class Contact actual constructor(bot: Bot, number: UInt) : Platfor
abstract override suspend fun sendXMLMessage(message: String)
/**
* 阻塞发送一个消息. 仅应在 Java 使用
*/
fun blockingSendMessage(chain: MessageChain) = runBlocking { sendMessage(chain) }
/**
* 阻塞发送一个消息. 仅应在 Java 使用
*/
fun blockingSendMessage(message: Message) = runBlocking { sendMessage(message) }
/**
* 阻塞发送一个消息. 仅应在 Java 使用
*/
fun blockingSendMessage(plain: String) = runBlocking { sendMessage(plain) }
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(chain: MessageChain) = bot.network.NetworkScope.launch { sendMessage(chain) }
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(message: Message) = bot.network.NetworkScope.launch { sendMessage(message) }
/**
* 异步发送一个消息. 仅应在 Java 使用
*/
fun asyncSendMessage(plain: String) = bot.network.NetworkScope.launch { sendMessage(plain) }
//
// /**
// * 阻塞发送一个消息. 仅应在 Java 使用
// */
// fun blockingSendMessage(chain: MessageChain) = runBlocking { sendMessage(chain) }
//
// /**
// * 阻塞发送一个消息. 仅应在 Java 使用
// */
// fun blockingSendMessage(message: Message) = runBlocking { sendMessage(message) }
//
// /**
// * 阻塞发送一个消息. 仅应在 Java 使用
// */
// fun blockingSendMessage(plain: String) = runBlocking { sendMessage(plain) }
//
// /**
// * 异步发送一个消息. 仅应在 Java 使用
// */
// fun asyncSendMessage(chain: MessageChain) = bot.network.launch { sendMessage(chain) }
//
// /**
// * 异步发送一个消息. 仅应在 Java 使用
// */
// fun asyncSendMessage(message: Message) = bot.network.launch { sendMessage(message) }
//
// /**
// * 异步发送一个消息. 仅应在 Java 使用
// */
// fun asyncSendMessage(plain: String) = bot.network.launch { sendMessage(plain) }
}
/**

View File

@ -0,0 +1,14 @@
package net.mamoe.mirai.event
import kotlinx.coroutines.runBlocking
// TODO 添加更多
/**
* Jvm 调用实现(阻塞)
*/
object Events {
@JvmStatic
fun <E : Event> subscribe(type: Class<E>, handler: suspend (E) -> ListeningStatus) =
runBlocking { type.kotlin.subscribe(handler) }
}

View File

@ -6,10 +6,13 @@ 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) {
internal actual suspend 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>)
consumer((it as KClass<out Event>).listeners() as EventListeners<in E>)
}
}
}

View File

@ -2,8 +2,11 @@
package net.mamoe.mirai.utils
import kotlinx.io.core.IoBuffer
import kotlinx.io.core.buildPacket
import kotlinx.io.streams.writePacket
import kotlinx.io.streams.asInput
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.OutputStream
import java.security.MessageDigest
@ -29,14 +32,33 @@ fun JavaBufferedImage.toMiraiImage(formatName: String = "gif"): BufferedImage {
}
fun BufferedImage.toJavaImage(): JavaBufferedImage = ImageIO.read(object : InputStream() {
override fun read(): Int = with(this@toJavaImage.data) {
if (remaining != 0L)
override fun read(): Int = with(this@toJavaImage.input) {
if (!endOfInput)
readByte().toInt()
else -1
}
})
/**
* 将缓存的图片写入流. 注意, 写入后缓存将会被清空.
*/
fun OutputStream.writeImage(image: BufferedImage) = this.writePacket(image.data)
fun File.toMiraiImage(): BufferedImage {
val image = ImageIO.getImageReaders(this.inputStream()).asSequence().first()
val digest = MessageDigest.getInstance("md5")
digest.reset()
FileInputStream(this).transferTo(object : OutputStream() {
override fun write(b: Int) {
b.toByte().let {
digest.update(it)
}
}
})
val dimension = image.defaultReadParam.sourceRenderSize
return BufferedImage(
width = dimension.width,
height = dimension.height,
md5 = digest.digest(),
format = image.formatName,
input = this.inputStream().asInput(IoBuffer.Pool),
inputSize = this.length()
)
}

View File

@ -4,15 +4,17 @@ package net.mamoe.mirai.utils
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.post
import io.ktor.content.ByteArrayContent
import io.ktor.client.request.HttpRequestBuilder
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.content.OutgoingContent
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.io.ByteWriteChannel
import kotlinx.coroutines.io.writeFully
import kotlinx.coroutines.withContext
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.readBytes
import kotlinx.io.core.readFully
import org.jsoup.Connection
import org.jsoup.Jsoup
import java.net.InetAddress
@ -22,7 +24,7 @@ import java.util.zip.CRC32
actual val deviceName: String = InetAddress.getLocalHost().hostName
/*
* TODO we may use libraries that provide these functions
* TODO: we may use libraries that provide these functions
*/
actual fun crc32(key: ByteArray): Int = CRC32().let { it.update(key); it.value.toInt() }
@ -33,56 +35,18 @@ actual fun solveIpAddress(hostname: String): String = InetAddress.getByName(host
actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress
/**
* Provided by Ktor Http
*/
private val httpClient: HttpClient = HttpClient {
engine { CIO }
}
actual suspend fun httpPostFriendImage(
suspend fun httpPostFriendImageOld(
uKeyHex: String,
fileSize: Long,
botNumber: UInt,
qq: UInt,
imageData: ByteReadPacket
): Boolean = (httpClient.post {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
parameters["htcmd"] = "0x6ff0070"
parameters["ver"] = "5603"
parameters["ukey"] = uKeyHex
parameters["filezise"] = imageData.remaining.toString()
parameters["range"] = 0.toString()
parameters["uin"] = qq.toString()
}
body = ByteArrayContent(imageData.readBytes(), ContentType.Image.Any)
} as HttpStatusCode).value == 200
//.postImage(imageData)
/**
* 上传群图片
*/
actual suspend fun httpPostGroupImage(
bot: UInt,
groupNumber: UInt,
uKeyHex: String,
fileSize: Long,
imageData: ByteReadPacket
): Boolean = Jsoup.connect("http://htdata2.qq.com/cgi-bin/httpconn" +
"?htcmd=0x6ff0071" +
"&term=pc" +
"?htcmd=0x6ff0070" +
"&ver=5603" +
"&filesize=${imageData.remaining}" +
"&uin=$bot" +
"&groupcode=$groupNumber" +
"&ukey=${uKeyHex}" +
"&filezise=${imageData.remaining}" +
"&range=0" +
"&ukey=" + uKeyHex)
"&uin=$botNumber"
)
.postImage(imageData)
@ -99,4 +63,24 @@ private suspend fun Connection.postImage(image: ByteReadPacket): Boolean = this
private suspend fun Connection.suspendExecute(): Connection.Response = withContext(Dispatchers.IO) {
execute()
}
internal actual val httpClient: HttpClient = HttpClient(CIO)
internal actual fun HttpRequestBuilder.configureBody(
inputSize: Long,
input: Input
) {
body = object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType = ContentType.Image.GIF
override val contentLength: Long = inputSize
override suspend fun writeTo(channel: ByteWriteChannel) {
val buffer = byteArrayOf(1)
while (!input.endOfInput) {
input.readFully(buffer)
channel.writeFully(buffer)
}
}
}
}

View File

@ -5,11 +5,10 @@ dependencies {
implementation project(':mirai-core')
compile files('./lib/jpcap.jar')
implementation rootProject.ext.coroutineCommon
implementation rootProject.ext.kotlinJvm
implementation rootProject.ext.kotlinxIOJvm
compile "org.jetbrains.kotlin:kotlin-reflect:1.3.50"
implementation 'org.jsoup:jsoup:1.12.1'
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-io', version: kotlinxio_version
api group: 'org.jetbrains.kotlin', name: 'kotlin-reflect', version: kotlin_version
}
tasks.withType(JavaCompile) {

View File

@ -1,12 +0,0 @@
import net.mamoe.mirai.utils.io.toUHexString
import org.jsoup.Connection
import org.jsoup.Jsoup
fun main() {
println(Jsoup.connect("http://61.183.164.34/gchatpic_new/B814D8D6A55D6DB423469E38D2A17AAA23836D74E7A656A4A8288C6078950A33B4A49E854E59B6D2314EFC47D6475902EDE8CADAFAF7F2A7670CAC05EA8314A1241128102F0A3AAF9C07284B1AE35E52F6D0A265235AFA6B/0?vuin=1040400290&term=255&srvver=26933&rf=n")
.cookie("ST", "00015DAA9C030040F3B82971FCBC718AA35573100B9CDBA2CB0DE38AF8710CA22E2986246345FC96B82BA0C211FDA700C397DF99FCC0989D67FD75F00B2FFB9CE0D032C3DCAC5A77")
.method(Connection.Method.GET)
.ignoreContentType(true)
.execute()
.bodyAsBytes().toUHexString())
}

View File

@ -3,6 +3,6 @@ apply plugin: "java"
dependencies {
compile project(":mirai-core")
compile rootProject.ext.coroutine
compile rootProject.ext.kotlinJvm
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlin_version
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutines_version
}

View File

@ -2,10 +2,7 @@
package demo1
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.contact.Group
@ -44,14 +41,14 @@ private fun readTestAccount(): BotAccount? {
}
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
suspend fun main() = coroutineScope {
val bot = Bot(readTestAccount() ?: BotAccount(//填写你的账号
account = 1994701121u,
password = "123456"
), PlatformLogger())
bot.login {
randomDeviceName = true
randomDeviceName = false
}.let {
if (it != LoginResult.SUCCESS) {
MiraiLogger.logError("Login failed: " + it.name)
@ -143,8 +140,7 @@ suspend fun main() {
demo2()
//由于使用的是协程, main函数执行完后就会结束程序.
delay(Long.MAX_VALUE)//永远等待, 以测试事件
bot.network.awaitDisconnection()//等到直到断开连接
}
@ -153,7 +149,7 @@ suspend fun main() {
* 对机器人说 "记笔记", 机器人记录之后的所有消息.
* 对机器人说 "停止", 机器人停止
*/
fun demo2() {
suspend fun demo2() {
subscribeAlways<FriendMessageEvent> { event ->
if (event.message eq "记笔记") {
subscribeUntilFalse<FriendMessageEvent> {