Improve Java-friendly APIs, add Java Future apis

This commit is contained in:
Him188 2020-03-01 18:09:16 +08:00
parent ed712295fe
commit 2cfd8be0f3
6 changed files with 712 additions and 81 deletions

View 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})"
}

View File

@ -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
}
}
}

View File

@ -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
}
/**

View File

@ -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 架构中

View 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})"
}

View File

@ -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
}
}
}