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:
parent
b7628cd3d6
commit
eed9c8f160
build.gradledependencies.gradlegradle.properties
mirai-console
mirai-core
build.gradle
src
commonMain/kotlin/net.mamoe.mirai
jvmMain/kotlin/net/mamoe/mirai
contact
event
utils
mirai-debug
mirai-demos/mirai-demo-1
18
build.gradle
18
build.gradle
@ -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')
|
||||
}
|
||||
|
||||
}
|
@ -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
18
gradle.properties
Normal 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
|
@ -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')
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
}
|
@ -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
|
@ -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
|
||||
)
|
@ -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))
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
@ -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)
|
@ -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) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) }
|
||||
}
|
@ -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>)
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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())
|
||||
}
|
@ -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
|
||||
}
|
||||
|
0
mirai-demos/mirai-demo-1/build.gradle.kts
Normal file
0
mirai-demos/mirai-demo-1/build.gradle.kts
Normal 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> {
|
||||
|
Loading…
Reference in New Issue
Block a user