Change Bot to interface

This commit is contained in:
Him188 2020-12-17 09:08:26 +08:00
parent b0d43eb708
commit 9c71a9c953
4 changed files with 181 additions and 114 deletions

View File

@ -20,8 +20,12 @@ import net.mamoe.mirai.contact.*
import net.mamoe.mirai.message.action.BotNudge
import net.mamoe.mirai.message.action.MemberNudge
import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlannedRemoval
import java.util.*
import kotlin.NoSuchElementException
/**
* 登录, 返回 [this]
@ -40,87 +44,31 @@ public suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
*
* @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式.
*/
public abstract class Bot internal constructor(
public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
/**
* Bot 配置
*/
public val configuration: BotConfiguration
) : CoroutineScope, ContactOrBot, UserOrBot {
public final override val coroutineContext: CoroutineContext = // for id
configuration.parentCoroutineContext
.plus(SupervisorJob(configuration.parentCoroutineContext[Job]))
.plus(configuration.parentCoroutineContext[CoroutineExceptionHandler]
?: CoroutineExceptionHandler { _, e ->
logger.error("An exception was thrown under a coroutine of Bot", e)
}
)
.plus(CoroutineName("Mirai Bot"))
public companion object {
@JvmField
@Suppress("ObjectPropertyName")
internal val _instances: LockFreeLinkedList<WeakRef<Bot>> = LockFreeLinkedList()
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@JvmStatic
public val botInstances: List<Bot>
get() = _instances.asSequence().mapNotNull { it.get() }.toList()
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@JvmStatic
public val botInstancesSequence: Sequence<Bot>
get() = _instances.asSequence().mapNotNull { it.get() }
/**
* 遍历每一个 [Bot] 实例
*/
@JvmSynthetic
public fun forEachInstance(block: (Bot) -> Unit): Unit = _instances.forEach { it.get()?.let(block) }
/**
* 获取一个 [Bot] 实例, 无对应实例时抛出 [NoSuchElementException]
*/
@JvmStatic
@Throws(NoSuchElementException::class)
public fun getInstance(qq: Long): Bot =
getInstanceOrNull(qq) ?: throw NoSuchElementException(qq.toString())
/**
* 获取一个 [Bot] 实例, 无对应实例时返回 `null`
*/
@JvmStatic
public fun getInstanceOrNull(qq: Long): Bot? =
_instances.asSequence().mapNotNull { it.get() }.firstOrNull { it.id == qq }
}
init {
_instances.addLast(this.weakRef())
supervisorJob.invokeOnCompletion {
_instances.removeIf { it.get()?.id == this.id }
}
}
/**
* QQ 号码. 实际类型为 uint
*/
public abstract override val id: Long
public override val id: Long
/**
* 昵称
*/
public abstract val nick: String
public val nick: String
/**
* 日志记录器
*/
public abstract val logger: MiraiLogger
public val logger: MiraiLogger
/**
* 判断 Bot 是否在线 (可正常收发消息)
* Bot 在线 (可正常收发消息) 时返回 `true`.
*/
public abstract val isOnline: Boolean
public val isOnline: Boolean
// region contacts
@ -128,18 +76,18 @@ public abstract class Bot internal constructor(
* [User.id] [Bot.id] 相同的 [Friend] 实例
*/
@MiraiExperimentalApi
public abstract val asFriend: Friend
public val asFriend: Friend
@Deprecated("Use asFriend instead", ReplaceWith("asFriend"))
@PlannedRemoval("2.0-M2")
public inline val selfQQ: Friend
public val selfQQ: Friend
get() = asFriend
/**
* 好友列表. 与服务器同步更新.
*/
public abstract val friends: ContactList<Friend>
public val friends: ContactList<Friend>
/**
* [对方 QQ 号码][id] 获取一个好友对象, 在获取失败时返回 `null`.
@ -155,7 +103,7 @@ public abstract class Bot internal constructor(
/**
* 加入的群列表. 与服务器同步更新.
*/
public abstract val groups: ContactList<Group>
public val groups: ContactList<Group>
/**
* [群号码][id] 获取一个群对象, 在获取失败时返回 `null`.
@ -180,7 +128,7 @@ public abstract class Bot internal constructor(
* @see alsoLogin `.apply { login() }` 捷径
*/
@JvmBlockingBridge
public abstract suspend fun login()
public suspend fun login()
/**
* 创建一个 "戳一戳" 消息
@ -200,44 +148,149 @@ public abstract class Bot internal constructor(
*
* @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭
*/
public abstract fun close(cause: Throwable? = null)
public fun close(cause: Throwable? = null)
public final override fun toString(): String = "Bot($id)"
public companion object {
@Suppress("ObjectPropertyName")
internal val _instances: WeakHashMap<Long, Bot> = WeakHashMap()
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@JvmStatic
public val instances: List<Bot>
get() = _instances.values.filterNotNull()
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@JvmStatic
public val instancesSequence: Sequence<Bot>
get() = _instances.values.asSequence().filterNotNull()
/**
* 获取一个 [Bot] 实例, 无对应实例时抛出 [NoSuchElementException]
*/
@JvmStatic
@Throws(NoSuchElementException::class)
public fun getInstance(qq: Long): Bot =
findInstance(qq) ?: throw NoSuchElementException(qq.toString())
/**
* 获取一个 [Bot] 实例, 无对应实例时返回 `null`
*/
@JvmStatic
public inline fun getInstanceOrNull(qq: Long): Bot? = findInstance(qq)
/**
* 获取一个 [Bot] 实例, 无对应实例时返回 `null`
*/
@JvmStatic
public fun findInstance(qq: Long): Bot? = _instances[qq]
// deprecated
/**
* 遍历每一个 [Bot] 实例
*/
@Deprecated(
"""
In Kotlin, use Sequence.forEach on instancesSequence.
In Java, use List.forEach on instances
""",
ReplaceWith("instancesSequence.forEach(block)", "net.mamoe.mirai.Bot.Companion.instancesSequence"),
DeprecationLevel.ERROR
)
@PlannedRemoval("2.0-M2")
public fun forEachInstance(block: (Bot) -> Unit): Unit = instancesSequence.forEach(block)
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@JvmStatic
@Deprecated(
"Use instances for shorter name.",
ReplaceWith("instances", "net.mamoe.mirai.Bot.Companion.instances"),
DeprecationLevel.ERROR
)
@PlannedRemoval("2.0-M2")
public val botInstances: List<Bot>
get() = instances
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@JvmStatic
@Deprecated(
"Use instancesSequence for shorter name.",
ReplaceWith("instancesSequence", "net.mamoe.mirai.Bot.Companion.instancesSequence"),
DeprecationLevel.ERROR
)
@PlannedRemoval("2.0-M2")
public val botInstancesSequence: Sequence<Bot>
get() = instancesSequence
}
/**
* 挂起协程直到 [Bot] 协程被关闭 ([Bot.close]).
* 即使 [Bot] 离线, 也会等待直到协程关闭.
*/
@JvmBlockingBridge
public suspend fun join(): Unit = supervisorJob.join()
/**
* 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放.
*
* : 不可重新登录. 必须重新实例化一个 [Bot].
*
* @param cause 原因. null 时视为正常关闭, null 时视为异常关闭
*/
@JvmBlockingBridge
public suspend fun closeAndJoin(cause: Throwable? = null) {
close(cause)
join()
}
}
/**
* 获取 [Job] 的协程 [Job]. [Job] 为一个 [SupervisorJob]
*/
@get:JvmSynthetic
public val Bot.supervisorJob: CompletableJob
public inline val Bot.supervisorJob: CompletableJob
get() = this.coroutineContext[Job] as CompletableJob
/**
* 挂起协程直到 [Bot] 协程被关闭 ([Bot.close]).
* 即使 [Bot] 离线, 也会等待直到协程关闭.
* [Bot] 拥有 [Friend.id] [id] 的好友时返回 `true`.
*/
@JvmSynthetic
public suspend inline fun Bot.join(): Unit = this.coroutineContext[Job]!!.join()
public inline fun Bot.containsFriend(id: Long): Boolean = this.friends.contains(id)
/**
* 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放.
*
* : 不可重新登录. 必须重新实例化一个 [Bot].
*
* @param cause 原因. null 时视为正常关闭, null 时视为异常关闭
* [Bot] 拥有 [Group.id] [id] 的群时返回 `true`.
*/
@JvmSynthetic
public inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id)
// deprecated
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // source compatibility
@PlannedRemoval("2.0-M2")
@JvmSynthetic
public suspend inline fun Bot.join(): Unit = join()
@Suppress("EXTENSION_SHADOWED_BY_MEMBER") // source compatibility
@PlannedRemoval("2.0-M2")
@JvmSynthetic
public suspend inline fun Bot.closeAndJoin(cause: Throwable? = null) {
close(cause)
coroutineContext[Job]?.join()
}
@JvmSynthetic
public inline fun Bot.containsFriend(id: Long): Boolean = this.friends.contains(id)
@JvmSynthetic
public inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id)
@Deprecated("Use getFriend", ReplaceWith("this.getFriend(id)"))
@PlannedRemoval("2.0-M2")
@JvmSynthetic

View File

@ -142,7 +142,7 @@ public interface Image : Message, MessageContent, CodableMessage {
@JvmStatic
@JvmBlockingBridge
public suspend fun Image.queryUrl(): String {
val bot = Bot._instances.peekFirst()?.get() ?: error("No Bot available to query image url")
val bot = Bot.instancesSequence.firstOrNull() ?: error("No Bot available to query image url")
return Mirai.queryImageUrl(bot, this)
}

View File

@ -36,11 +36,32 @@ import kotlin.coroutines.CoroutineContext
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
internal abstract class BotImpl<N : BotNetworkHandler> constructor(
configuration: BotConfiguration
) : Bot(configuration), CoroutineScope {
internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
final override val configuration: BotConfiguration,
final override val id: Long,
) : Bot, CoroutineScope {
// FASTEST INIT
init {
Bot._instances[this.id] = this
supervisorJob.invokeOnCompletion {
Bot._instances.remove(id)
}
}
final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) }
final override val coroutineContext: CoroutineContext = // for id
configuration.parentCoroutineContext
.plus(SupervisorJob(configuration.parentCoroutineContext[Job]))
.plus(configuration.parentCoroutineContext[CoroutineExceptionHandler]
?: CoroutineExceptionHandler { _, e ->
logger.error("An exception was thrown under a coroutine of Bot", e)
}
)
.plus(CoroutineName("Mirai Bot"))
// region network
val network: N get() = _network
@ -60,8 +81,8 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
@OptIn(ExperimentalTime::class)
@Suppress("unused")
private val offlineListener: Listener<BotOfflineEvent> =
this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
if (event.bot != this@BotImpl) {
this@AbstractBot.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
if (event.bot != this@AbstractBot) {
return@subscribeAlways
}
if (!event.bot.isActive) {
@ -107,7 +128,7 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
}
network.withConnectionLock {
/**
* [BotImpl.relogin] only, no [BotNetworkHandler.init]
* [AbstractBot.relogin] only, no [BotNetworkHandler.init]
*/
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause)
@ -161,7 +182,7 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
/**
* **Exposed public API**
* [BotImpl.relogin] && [BotNetworkHandler.init]
* [AbstractBot.relogin] && [BotNetworkHandler.init]
*/
final override suspend fun login() {
@ThisApiMustBeUsedInWithConnectionLockBlock
@ -266,7 +287,7 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
return
}
GlobalScope.launch {
runCatching { BotOfflineEvent.Active(this@BotImpl, cause).broadcast() }.exceptionOrNull()
runCatching { BotOfflineEvent.Active(this@AbstractBot, cause).broadcast() }.exceptionOrNull()
?.let { logger.error(it) }
}
@ -278,6 +299,8 @@ internal abstract class BotImpl<N : BotNetworkHandler> constructor(
}
}
}
final override fun toString(): String = "Bot($id)"
}
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)

View File

@ -42,25 +42,16 @@ internal fun Bot.asQQAndroidBot(): QQAndroidBot {
internal class QQAndroidBot constructor(
account: BotAccount,
configuration: BotConfiguration
) : QQAndroidBotBase(account, configuration)
internal abstract class QQAndroidBotBase constructor(
private val account: BotAccount,
configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(configuration) {
) : AbstractBot<QQAndroidBotNetworkHandler>(configuration, account.id) {
@Suppress("LeakingThis")
val client: QQAndroidClient =
QQAndroidClient(
account,
bot = this as QQAndroidBot,
bot = this,
device = configuration.deviceInfo?.invoke(this) ?: DeviceInfo.random()
)
internal var firstLoginSucceed: Boolean = false
override val id: Long
get() = account.id
inline val json get() = configuration.json
override val friends: ContactList<Friend> = ContactList()
@ -78,12 +69,14 @@ internal abstract class QQAndroidBotBase constructor(
override val asFriend: Friend by lazy {
@OptIn(LowLevelApi::class)
Mirai._lowLevelNewFriend(this, object : FriendInfo {
override val uin: Long get() = this@QQAndroidBotBase.id
override val nick: String get() = this@QQAndroidBotBase.nick
override val uin: Long get() = this@QQAndroidBot.id
override val nick: String get() = this@QQAndroidBot.nick
override val remark: String get() = ""
})
}
override val groups: ContactList<Group> = ContactList()
/**
* Final process for 'login'
*/
@ -96,11 +89,9 @@ internal abstract class QQAndroidBotBase constructor(
}
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
return QQAndroidBotNetworkHandler(coroutineContext, this as QQAndroidBot)
return QQAndroidBotNetworkHandler(coroutineContext, this)
}
override val groups: ContactList<Group> = ContactList()
@JvmField
val groupListModifyLock = Mutex()