mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-01 09:27:03 +08:00
Improve Java-friendly APIs, add Java Future
apis
This commit is contained in:
parent
ed712295fe
commit
2cfd8be0f3
236
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt
Normal file
236
mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt
Normal file
@ -0,0 +1,236 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.data.FriendInfo
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
||||
/**
|
||||
* 机器人对象. 一个机器人实例登录一个 QQ 账号.
|
||||
* Mirai 为多账号设计, 可同时维护多个机器人.
|
||||
*
|
||||
* 注: Bot 为全协程实现, 没有其他任务时若不使用 [join], 主线程将会退出.
|
||||
*
|
||||
* @see Contact 联系人
|
||||
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@UseExperimental(MiraiInternalAPI::class, LowLevelAPI::class, MiraiExperimentalAPI::class, JavaHappyAPI::class)
|
||||
actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
actual companion object {
|
||||
/**
|
||||
* 复制一份此时的 [Bot] 实例列表.
|
||||
*/
|
||||
@JvmStatic
|
||||
actual val instances: List<WeakRef<Bot>>
|
||||
get() = BotImpl.instances.toList()
|
||||
|
||||
/**
|
||||
* 遍历每一个 [Bot] 实例
|
||||
*/
|
||||
actual inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
|
||||
|
||||
/**
|
||||
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
|
||||
*/
|
||||
@JvmStatic
|
||||
actual fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Bot] 运行的 [Context].
|
||||
*
|
||||
* 在 JVM 的默认实现为 `class ContextImpl : Context`
|
||||
* 在 Android 实现为 `android.content.Context`
|
||||
*/
|
||||
actual abstract val context: Context
|
||||
|
||||
/**
|
||||
* 账号信息
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
actual abstract val account: BotAccount
|
||||
|
||||
/**
|
||||
* QQ 号码. 实际类型为 uint
|
||||
*/
|
||||
actual abstract val uin: Long
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual val nick: String
|
||||
get() = ""// TODO("bot 昵称获取")
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
*/
|
||||
actual abstract val logger: MiraiLogger
|
||||
|
||||
// region contacts
|
||||
|
||||
actual abstract val selfQQ: QQ
|
||||
|
||||
/**
|
||||
* 机器人的好友列表. 它将与服务器同步更新
|
||||
*/
|
||||
actual abstract val qqs: ContactList<QQ>
|
||||
|
||||
/**
|
||||
* 获取一个好友或一个群.
|
||||
* 在一些情况下这可能会造成歧义. 请考虑后使用.
|
||||
*/
|
||||
actual operator fun get(id: Long): Contact {
|
||||
return this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException("contact id $id")
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有这个 id 的好友或群.
|
||||
* 在一些情况下这可能会造成歧义. 请考虑后使用.
|
||||
*/
|
||||
actual operator fun contains(id: Long): Boolean {
|
||||
return this.qqs.contains(id) || this.groups.contains(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException]
|
||||
*/
|
||||
actual fun getFriend(id: Long): QQ {
|
||||
if (id == uin) return selfQQ
|
||||
return qqs.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such friend $id for bot ${this.uin}")
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个 [QQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]).
|
||||
*
|
||||
* [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job.
|
||||
* 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
actual abstract fun QQ(friendInfo: FriendInfo): QQ
|
||||
|
||||
/**
|
||||
* 机器人加入的群列表.
|
||||
*/
|
||||
actual abstract val groups: ContactList<Group>
|
||||
|
||||
/**
|
||||
* 获取一个机器人加入的群.
|
||||
*
|
||||
* @throws NoSuchElementException 当不存在这个群时
|
||||
*/
|
||||
actual fun getGroup(id: Long): Group {
|
||||
return groups.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such group $id for bot ${this.uin}")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region network
|
||||
|
||||
/**
|
||||
* 网络模块
|
||||
*/
|
||||
actual abstract val network: BotNetworkHandler
|
||||
|
||||
/**
|
||||
* 挂起直到 [Bot] 下线.
|
||||
*/
|
||||
@JvmName("joinSuspend")
|
||||
@JvmSynthetic
|
||||
actual suspend inline fun join() = network.join()
|
||||
|
||||
/**
|
||||
* 登录, 或重新登录.
|
||||
* 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表.
|
||||
*
|
||||
* 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况.
|
||||
*
|
||||
* 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin]
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
@JvmName("loginSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun login()
|
||||
// endregion
|
||||
|
||||
|
||||
// region actions
|
||||
|
||||
/**
|
||||
* 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
|
||||
*
|
||||
* [Bot] 撤回自己的消息不需要权限.
|
||||
* [Bot] 撤回群员的消息需要管理员权限.
|
||||
*
|
||||
* @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得.
|
||||
*
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
*
|
||||
* @see Bot.recall (扩展函数) 接受参数 [MessageChain]
|
||||
* @see _lowLevelRecallFriendMessage 低级 API
|
||||
* @see _lowLevelRecallGroupMessage 低级 API
|
||||
*/
|
||||
@JvmName("recallSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun recall(source: MessageSource)
|
||||
|
||||
/**
|
||||
* 获取图片下载链接
|
||||
*/
|
||||
@JvmName("queryImageUrlSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun queryImageUrl(image: Image): String
|
||||
|
||||
/**
|
||||
* 获取图片下载链接并开始下载.
|
||||
*
|
||||
* @see ByteReadChannel.copyAndClose
|
||||
* @see ByteReadChannel.copyTo
|
||||
*/
|
||||
@JvmName("openChannelSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun openChannel(image: Image): ByteReadChannel
|
||||
|
||||
/**
|
||||
* 添加一个好友
|
||||
*
|
||||
* @param message 若需要验证请求时的验证消息.
|
||||
* @param remark 好友备注
|
||||
*/
|
||||
@JvmName("addFriendSuspend")
|
||||
@JvmSynthetic
|
||||
@MiraiExperimentalAPI("未支持")
|
||||
actual abstract suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 关闭这个 [Bot], 立即取消 [Bot] 的 [kotlinx.coroutines.SupervisorJob].
|
||||
* 之后 [kotlinx.coroutines.isActive] 将会返回 `false`.
|
||||
*
|
||||
* **注意:** 不可重新登录. 必须重新实例化一个 [Bot].
|
||||
*
|
||||
* @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭
|
||||
*
|
||||
* @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭
|
||||
*/
|
||||
actual abstract fun close(cause: Throwable?)
|
||||
|
||||
@UseExperimental(LowLevelAPI::class, MiraiExperimentalAPI::class)
|
||||
actual final override fun toString(): String = "Bot(${uin})"
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
package net.mamoe.mirai
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* [Bot] 中为了让 Java 使用者调用更方便的 API 列表.
|
||||
@ -14,42 +19,135 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
actual abstract class BotJavaHappyAPI actual constructor() {
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
check(this is Bot)
|
||||
assert(this is Bot)
|
||||
}
|
||||
|
||||
private inline fun <R> runBlocking(crossinline block: suspend Bot.() -> R): R {
|
||||
return kotlinx.coroutines.runBlocking { block(this@BotJavaHappyAPI as Bot) }
|
||||
}
|
||||
|
||||
private inline fun <R> future(crossinline block: suspend Bot.() -> R): Future<R> {
|
||||
return (this as Bot).run { future(block) }
|
||||
}
|
||||
|
||||
|
||||
@JvmName("login")
|
||||
actual open fun __loginBlockingForJava__() {
|
||||
fun __loginBlockingForJava__() {
|
||||
runBlocking { login() }
|
||||
}
|
||||
|
||||
@JvmName("recall")
|
||||
actual open fun __recallBlockingForJava__(source: MessageSource) {
|
||||
fun __recallBlockingForJava__(source: MessageSource) {
|
||||
runBlocking { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("recall")
|
||||
fun __recallBlockingForJava__(source: MessageChain) {
|
||||
runBlocking { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("recallIn")
|
||||
fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) {
|
||||
runBlocking { recallIn(source, millis) }
|
||||
}
|
||||
|
||||
@JvmName("recallIn")
|
||||
fun __recallIn_MemberForJava__(source: MessageChain, millis: Long) {
|
||||
runBlocking { recallIn(source, millis) }
|
||||
}
|
||||
|
||||
@JvmName("queryImageUrl")
|
||||
actual open fun __queryImageUrlBlockingForJava__(image: Image): String {
|
||||
fun __queryImageUrlBlockingForJava__(image: Image): String {
|
||||
return runBlocking { queryImageUrl(image) }
|
||||
}
|
||||
|
||||
@JvmName("join")
|
||||
actual open fun __joinBlockingForJava__() {
|
||||
fun __joinBlockingForJava__() {
|
||||
runBlocking { join() }
|
||||
}
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
@JvmOverloads
|
||||
@JvmName("addFriend")
|
||||
actual open fun __addFriendBlockingForJava__(
|
||||
fun __addFriendBlockingForJava__(
|
||||
id: Long,
|
||||
message: String?,
|
||||
remark: String?
|
||||
message: String? = null,
|
||||
remark: String? = null
|
||||
): AddFriendResult {
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
return runBlocking { addFriend(id, message, remark) }
|
||||
}
|
||||
|
||||
@JvmName("loginAsync")
|
||||
fun __loginAsyncForJava__(): Future<Unit> {
|
||||
return future { login() }
|
||||
}
|
||||
|
||||
@JvmName("recallAsync")
|
||||
fun __recallAsyncForJava__(source: MessageSource): Future<Unit> {
|
||||
return future { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("recallAsync")
|
||||
fun __recallAsyncForJava__(source: MessageChain): Future<Unit> {
|
||||
return future { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("queryImageUrlAsync")
|
||||
fun __queryImageUrlAsyncForJava__(image: Image): Future<String> {
|
||||
return future { queryImageUrl(image) }
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <R> Bot.future(crossinline block: suspend Bot.() -> R): Future<R> {
|
||||
return object : Future<R> {
|
||||
val value: CompletableDeferred<R> = CompletableDeferred()
|
||||
|
||||
init {
|
||||
launch {
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
value.completeWith(kotlin.runCatching { block() })
|
||||
}
|
||||
}
|
||||
|
||||
override fun isDone(): Boolean {
|
||||
return value.isCompleted
|
||||
}
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
override fun get(): R {
|
||||
if (value.isCompleted) {
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
return value.getCompleted()
|
||||
}
|
||||
return runBlocking { value.await() }
|
||||
}
|
||||
|
||||
override fun get(timeout: Long, unit: TimeUnit): R {
|
||||
if (value.isCompleted) {
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
return value.getCompleted()
|
||||
}
|
||||
return runBlocking {
|
||||
withTimeoutOrNull(TimeUnit.MILLISECONDS.convert(timeout, unit)) { value.await() }
|
||||
?: throw TimeoutException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
|
||||
if (value.isCompleted || value.isCancelled) {
|
||||
return false
|
||||
}
|
||||
|
||||
return if (mayInterruptIfRunning && value.isActive) {
|
||||
value.cancel()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return value.isCancelled
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ import net.mamoe.mirai.utils.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmOverloads
|
||||
import kotlin.jvm.JvmStatic
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
@ -43,25 +44,24 @@ import kotlin.jvm.JvmSynthetic
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@UseExperimental(MiraiInternalAPI::class, LowLevelAPI::class)
|
||||
abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
|
||||
companion object {
|
||||
/**
|
||||
* 复制一份此时的 [Bot] 实例列表.
|
||||
*/
|
||||
@JvmStatic
|
||||
val instances: List<WeakRef<Bot>>
|
||||
get() = BotImpl.instances.toList()
|
||||
|
||||
/**
|
||||
* 遍历每一个 [Bot] 实例
|
||||
*/
|
||||
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
|
||||
inline fun forEachInstance(block: (Bot) -> Unit)
|
||||
|
||||
/**
|
||||
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
|
||||
*/
|
||||
@JvmStatic
|
||||
fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq)
|
||||
fun getInstance(qq: Long): Bot
|
||||
}
|
||||
|
||||
/**
|
||||
@ -88,7 +88,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
val nick: String
|
||||
get() = ""// TODO("bot 昵称获取")
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
@ -108,26 +107,18 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
* 获取一个好友或一个群.
|
||||
* 在一些情况下这可能会造成歧义. 请考虑后使用.
|
||||
*/
|
||||
operator fun get(id: Long): Contact {
|
||||
return this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException("contact id $id")
|
||||
}
|
||||
operator fun get(id: Long): Contact
|
||||
|
||||
/**
|
||||
* 判断是否有这个 id 的好友或群.
|
||||
* 在一些情况下这可能会造成歧义. 请考虑后使用.
|
||||
*/
|
||||
operator fun contains(id: Long): Boolean {
|
||||
return this.qqs.contains(id) || this.groups.contains(id)
|
||||
}
|
||||
operator fun contains(id: Long): Boolean
|
||||
|
||||
/**
|
||||
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException]
|
||||
*/
|
||||
fun getFriend(id: Long): QQ {
|
||||
if (id == uin) return selfQQ
|
||||
return qqs.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such friend $id for bot ${this.uin}")
|
||||
}
|
||||
fun getFriend(id: Long): QQ
|
||||
|
||||
/**
|
||||
* 构造一个 [QQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]).
|
||||
@ -147,10 +138,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
*
|
||||
* @throws NoSuchElementException 当不存在这个群时
|
||||
*/
|
||||
fun getGroup(id: Long): Group {
|
||||
return groups.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such group $id for bot ${this.uin}")
|
||||
}
|
||||
fun getGroup(id: Long): Group
|
||||
|
||||
// endregion
|
||||
|
||||
@ -166,7 +154,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
*/
|
||||
@JvmName("joinSuspend")
|
||||
@JvmSynthetic
|
||||
suspend inline fun join() = network.join()
|
||||
suspend inline fun join()
|
||||
|
||||
/**
|
||||
* 登录, 或重新登录.
|
||||
@ -244,31 +232,11 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
*
|
||||
* @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭
|
||||
*/
|
||||
@JvmOverloads
|
||||
abstract fun close(cause: Throwable? = null)
|
||||
|
||||
@UseExperimental(LowLevelAPI::class, MiraiExperimentalAPI::class)
|
||||
final override fun toString(): String = "Bot(${uin})"
|
||||
|
||||
@JvmName("login")
|
||||
@JavaHappyAPI
|
||||
override fun __loginBlockingForJava__() = super.__loginBlockingForJava__()
|
||||
|
||||
@JvmName("recall")
|
||||
@JavaHappyAPI
|
||||
override fun __recallBlockingForJava__(source: MessageSource) = super.__recallBlockingForJava__(source)
|
||||
|
||||
@JvmName("queryImageUrl")
|
||||
@JavaHappyAPI
|
||||
override fun __queryImageUrlBlockingForJava__(image: Image): String = super.__queryImageUrlBlockingForJava__(image)
|
||||
|
||||
@JvmName("addFriend")
|
||||
@JavaHappyAPI
|
||||
override fun __addFriendBlockingForJava__(id: Long, message: String?, remark: String?) =
|
||||
super.__addFriendBlockingForJava__(id, message, remark)
|
||||
|
||||
@JvmName("join")
|
||||
@JavaHappyAPI
|
||||
override fun __joinBlockingForJava__() = super.__joinBlockingForJava__()
|
||||
final override fun toString(): String
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9,12 +9,7 @@
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmOverloads
|
||||
|
||||
/**
|
||||
* 表明这个 API 是为了让 Java 使用者调用更方便.
|
||||
@ -26,23 +21,13 @@ annotation class JavaHappyAPI
|
||||
|
||||
/**
|
||||
* [Bot] 中为了让 Java 使用者调用更方便的 API 列表.
|
||||
*/ // TODO: 2020/3/1 待 https://youtrack.jetbrains.com/issue/KT-36740 修复后添加 Future 相关 API.
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "unused")
|
||||
expect abstract class BotJavaHappyAPI() { // 不要使用 interface, 会无法添加默认实现
|
||||
@JvmName("join")
|
||||
open fun __joinBlockingForJava__()
|
||||
}
|
||||
|
||||
@JvmName("login")
|
||||
open fun __loginBlockingForJava__()
|
||||
// 保留多平台结构, 以避免在 Android 和 JVM 都定义这个类 ---- 这会造成代码重复.
|
||||
// 待 https://youtrack.jetbrains.com/issue/KT-27801 实现后修改为 hierarchical MPP 架构
|
||||
|
||||
@JvmName("recall")
|
||||
open fun __recallBlockingForJava__(source: MessageSource)
|
||||
|
||||
@JvmName("queryImageUrl")
|
||||
open fun __queryImageUrlBlockingForJava__(image: Image): String
|
||||
|
||||
@JvmName("addFriend")
|
||||
@JvmOverloads
|
||||
open fun __addFriendBlockingForJava__(id: Long, message: String? = null, remark: String? = null): AddFriendResult
|
||||
}
|
||||
// 待 https://youtrack.jetbrains.com/issue/KT-36740 修复后添加 Future 相关 API 到 hierarchical MPP 架构中
|
246
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt
Normal file
246
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt
Normal file
@ -0,0 +1,246 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai
|
||||
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.data.FriendInfo
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.network.BotNetworkHandler
|
||||
import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
||||
/**
|
||||
* 机器人对象. 一个机器人实例登录一个 QQ 账号.
|
||||
* Mirai 为多账号设计, 可同时维护多个机器人.
|
||||
*
|
||||
* 注: Bot 为全协程实现, 没有其他任务时若不使用 [join], 主线程将会退出.
|
||||
*
|
||||
* @see Contact 联系人
|
||||
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@UseExperimental(MiraiInternalAPI::class, LowLevelAPI::class, MiraiExperimentalAPI::class, JavaHappyAPI::class)
|
||||
actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() {
|
||||
actual companion object {
|
||||
/**
|
||||
* 复制一份此时的 [Bot] 实例列表.
|
||||
*/
|
||||
@JvmStatic
|
||||
actual val instances: List<WeakRef<Bot>>
|
||||
get() = BotImpl.instances.toList()
|
||||
|
||||
/**
|
||||
* 遍历每一个 [Bot] 实例
|
||||
*/
|
||||
@JvmName("forEachInstanceKotlin")
|
||||
@JvmSynthetic
|
||||
actual inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
|
||||
|
||||
/**
|
||||
* 遍历每一个 [Bot] 实例
|
||||
*/
|
||||
@JavaHappyAPI
|
||||
@JvmName("forEachInstance")
|
||||
@Suppress("FunctionName")
|
||||
fun __forEachInstanceForJava__(block: (Bot) -> Unit) = forEachInstance(block)
|
||||
|
||||
/**
|
||||
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
|
||||
*/
|
||||
@JvmStatic
|
||||
actual fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq)
|
||||
}
|
||||
|
||||
/**
|
||||
* [Bot] 运行的 [Context].
|
||||
*
|
||||
* 在 JVM 的默认实现为 `class ContextImpl : Context`
|
||||
* 在 Android 实现为 `android.content.Context`
|
||||
*/
|
||||
actual abstract val context: Context
|
||||
|
||||
/**
|
||||
* 账号信息
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
actual abstract val account: BotAccount
|
||||
|
||||
/**
|
||||
* QQ 号码. 实际类型为 uint
|
||||
*/
|
||||
actual abstract val uin: Long
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual val nick: String
|
||||
get() = ""// TODO("bot 昵称获取")
|
||||
|
||||
/**
|
||||
* 日志记录器
|
||||
*/
|
||||
actual abstract val logger: MiraiLogger
|
||||
|
||||
// region contacts
|
||||
|
||||
actual abstract val selfQQ: QQ
|
||||
|
||||
/**
|
||||
* 机器人的好友列表. 它将与服务器同步更新
|
||||
*/
|
||||
actual abstract val qqs: ContactList<QQ>
|
||||
|
||||
/**
|
||||
* 获取一个好友或一个群.
|
||||
* 在一些情况下这可能会造成歧义. 请考虑后使用.
|
||||
*/
|
||||
actual operator fun get(id: Long): Contact {
|
||||
return this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException("contact id $id")
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否有这个 id 的好友或群.
|
||||
* 在一些情况下这可能会造成歧义. 请考虑后使用.
|
||||
*/
|
||||
actual operator fun contains(id: Long): Boolean {
|
||||
return this.qqs.contains(id) || this.groups.contains(id)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException]
|
||||
*/
|
||||
actual fun getFriend(id: Long): QQ {
|
||||
if (id == uin) return selfQQ
|
||||
return qqs.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such friend $id for bot ${this.uin}")
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造一个 [QQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]).
|
||||
*
|
||||
* [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job.
|
||||
* 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭.
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
actual abstract fun QQ(friendInfo: FriendInfo): QQ
|
||||
|
||||
/**
|
||||
* 机器人加入的群列表.
|
||||
*/
|
||||
actual abstract val groups: ContactList<Group>
|
||||
|
||||
/**
|
||||
* 获取一个机器人加入的群.
|
||||
*
|
||||
* @throws NoSuchElementException 当不存在这个群时
|
||||
*/
|
||||
actual fun getGroup(id: Long): Group {
|
||||
return groups.delegate.getOrNull(id)
|
||||
?: throw NoSuchElementException("No such group $id for bot ${this.uin}")
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region network
|
||||
|
||||
/**
|
||||
* 网络模块
|
||||
*/
|
||||
actual abstract val network: BotNetworkHandler
|
||||
|
||||
/**
|
||||
* 挂起直到 [Bot] 下线.
|
||||
*/
|
||||
@JvmName("joinSuspend")
|
||||
@JvmSynthetic
|
||||
actual suspend inline fun join() = network.join()
|
||||
|
||||
/**
|
||||
* 登录, 或重新登录.
|
||||
* 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表.
|
||||
*
|
||||
* 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况.
|
||||
*
|
||||
* 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin]
|
||||
*
|
||||
* @throws LoginFailedException
|
||||
*/
|
||||
@JvmName("loginSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun login()
|
||||
// endregion
|
||||
|
||||
|
||||
// region actions
|
||||
|
||||
/**
|
||||
* 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
|
||||
*
|
||||
* [Bot] 撤回自己的消息不需要权限.
|
||||
* [Bot] 撤回群员的消息需要管理员权限.
|
||||
*
|
||||
* @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得.
|
||||
*
|
||||
* @throws PermissionDeniedException 当 [Bot] 无权限操作时
|
||||
*
|
||||
* @see Bot.recall (扩展函数) 接受参数 [MessageChain]
|
||||
* @see _lowLevelRecallFriendMessage 低级 API
|
||||
* @see _lowLevelRecallGroupMessage 低级 API
|
||||
*/
|
||||
@JvmName("recallSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun recall(source: MessageSource)
|
||||
|
||||
/**
|
||||
* 获取图片下载链接
|
||||
*/
|
||||
@JvmName("queryImageUrlSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun queryImageUrl(image: Image): String
|
||||
|
||||
/**
|
||||
* 获取图片下载链接并开始下载.
|
||||
*
|
||||
* @see ByteReadChannel.copyAndClose
|
||||
* @see ByteReadChannel.copyTo
|
||||
*/
|
||||
@JvmName("openChannelSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun openChannel(image: Image): ByteReadChannel
|
||||
|
||||
/**
|
||||
* 添加一个好友
|
||||
*
|
||||
* @param message 若需要验证请求时的验证消息.
|
||||
* @param remark 好友备注
|
||||
*/
|
||||
@JvmName("addFriendSuspend")
|
||||
@JvmSynthetic
|
||||
@MiraiExperimentalAPI("未支持")
|
||||
actual abstract suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult
|
||||
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* 关闭这个 [Bot], 立即取消 [Bot] 的 [kotlinx.coroutines.SupervisorJob].
|
||||
* 之后 [kotlinx.coroutines.isActive] 将会返回 `false`.
|
||||
*
|
||||
* **注意:** 不可重新登录. 必须重新实例化一个 [Bot].
|
||||
*
|
||||
* @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭
|
||||
*
|
||||
* @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭
|
||||
*/
|
||||
actual abstract fun close(cause: Throwable?)
|
||||
|
||||
@UseExperimental(LowLevelAPI::class, MiraiExperimentalAPI::class)
|
||||
actual final override fun toString(): String = "Bot(${uin})"
|
||||
}
|
@ -1,10 +1,15 @@
|
||||
package net.mamoe.mirai
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* [Bot] 中为了让 Java 使用者调用更方便的 API 列表.
|
||||
@ -14,42 +19,135 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
actual abstract class BotJavaHappyAPI actual constructor() {
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
check(this is Bot)
|
||||
assert(this is Bot)
|
||||
}
|
||||
|
||||
private inline fun <R> runBlocking(crossinline block: suspend Bot.() -> R): R {
|
||||
return kotlinx.coroutines.runBlocking { block(this@BotJavaHappyAPI as Bot) }
|
||||
}
|
||||
|
||||
private inline fun <R> future(crossinline block: suspend Bot.() -> R): Future<R> {
|
||||
return (this as Bot).run { future(block) }
|
||||
}
|
||||
|
||||
|
||||
@JvmName("login")
|
||||
actual open fun __loginBlockingForJava__() {
|
||||
fun __loginBlockingForJava__() {
|
||||
runBlocking { login() }
|
||||
}
|
||||
|
||||
@JvmName("recall")
|
||||
actual open fun __recallBlockingForJava__(source: MessageSource) {
|
||||
fun __recallBlockingForJava__(source: MessageSource) {
|
||||
runBlocking { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("recall")
|
||||
fun __recallBlockingForJava__(source: MessageChain) {
|
||||
runBlocking { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("recallIn")
|
||||
fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) {
|
||||
runBlocking { recallIn(source, millis) }
|
||||
}
|
||||
|
||||
@JvmName("recallIn")
|
||||
fun __recallIn_MemberForJava__(source: MessageChain, millis: Long) {
|
||||
runBlocking { recallIn(source, millis) }
|
||||
}
|
||||
|
||||
@JvmName("queryImageUrl")
|
||||
actual open fun __queryImageUrlBlockingForJava__(image: Image): String {
|
||||
fun __queryImageUrlBlockingForJava__(image: Image): String {
|
||||
return runBlocking { queryImageUrl(image) }
|
||||
}
|
||||
|
||||
@JvmName("join")
|
||||
actual open fun __joinBlockingForJava__() {
|
||||
fun __joinBlockingForJava__() {
|
||||
runBlocking { join() }
|
||||
}
|
||||
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
@JvmOverloads
|
||||
@JvmName("addFriend")
|
||||
actual open fun __addFriendBlockingForJava__(
|
||||
fun __addFriendBlockingForJava__(
|
||||
id: Long,
|
||||
message: String?,
|
||||
remark: String?
|
||||
message: String? = null,
|
||||
remark: String? = null
|
||||
): AddFriendResult {
|
||||
@UseExperimental(MiraiExperimentalAPI::class)
|
||||
return runBlocking { addFriend(id, message, remark) }
|
||||
}
|
||||
|
||||
@JvmName("loginAsync")
|
||||
fun __loginAsyncForJava__(): Future<Unit> {
|
||||
return future { login() }
|
||||
}
|
||||
|
||||
@JvmName("recallAsync")
|
||||
fun __recallAsyncForJava__(source: MessageSource): Future<Unit> {
|
||||
return future { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("recallAsync")
|
||||
fun __recallAsyncForJava__(source: MessageChain): Future<Unit> {
|
||||
return future { recall(source) }
|
||||
}
|
||||
|
||||
@JvmName("queryImageUrlAsync")
|
||||
fun __queryImageUrlAsyncForJava__(image: Image): Future<String> {
|
||||
return future { queryImageUrl(image) }
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <R> Bot.future(crossinline block: suspend Bot.() -> R): Future<R> {
|
||||
return object : Future<R> {
|
||||
val value: CompletableDeferred<R> = CompletableDeferred()
|
||||
|
||||
init {
|
||||
launch {
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
value.completeWith(kotlin.runCatching { block() })
|
||||
}
|
||||
}
|
||||
|
||||
override fun isDone(): Boolean {
|
||||
return value.isCompleted
|
||||
}
|
||||
|
||||
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
|
||||
override fun get(): R {
|
||||
if (value.isCompleted) {
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
return value.getCompleted()
|
||||
}
|
||||
return runBlocking { value.await() }
|
||||
}
|
||||
|
||||
override fun get(timeout: Long, unit: TimeUnit): R {
|
||||
if (value.isCompleted) {
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
return value.getCompleted()
|
||||
}
|
||||
return runBlocking {
|
||||
withTimeoutOrNull(TimeUnit.MILLISECONDS.convert(timeout, unit)) { value.await() }
|
||||
?: throw TimeoutException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
|
||||
if (value.isCompleted || value.isCancelled) {
|
||||
return false
|
||||
}
|
||||
|
||||
return if (mayInterruptIfRunning && value.isActive) {
|
||||
value.cancel()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isCancelled(): Boolean {
|
||||
return value.isCancelled
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user