diff --git a/README.md b/README.md index 95213ed1c..c8715ef00 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ Mirai 源码完全开放, 您可以参考 Mirai 的协议实现来开发其他 - (社区)`JavaScript`(`Node.js`): [node-mirai](https://github.com/RedBeanN/node-mirai) mirai 的 Node.js SDK - (社区)`Go`: [gomirai](https://github.com/Logiase/gomirai) 基于 mirai-api-http 的 GoLang SDK - (社区)`Mozilla Rhino`: [mirai-rhinojs-sdk](https://github.com/StageGuard/mirai-rhinojs-sdk) +- (社区)`Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) - (官方)其他任意语言:使用由 [mirai-api-http](https://github.com/mamoe/mirai-api-http) 提供的 http 接口进行接入 #### 使用 mirai 为第三方依赖库引入项目 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt index 31bedfc44..63022dcd5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("INAPPLICABLE_JVM_NAME") +@file:Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") @file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class) package net.mamoe.mirai.qqandroid.contact @@ -93,7 +93,7 @@ internal class GroupImpl( @OptIn(MiraiExperimentalAPI::class) override lateinit var botPermission: MemberPermission - var _botMuteTimestamp: Int = groupInfo.botMuteRemaining + var _botMuteTimestamp: Int = groupInfo.botMuteTimestamp override val botMuteRemaining: Int = if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt index 8f591f234..9a0b42624 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR") package net.mamoe.mirai.qqandroid.contact @@ -196,9 +196,13 @@ internal class MemberImpl constructor( net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast() } + @OptIn(MiraiInternalAPI::class) @JvmSynthetic override suspend fun kick(message: String) { checkBotPermissionHigherThanThis() + check(group.members.getOrNull(this.id) != null) { + "Member ${this.id} had already been kicked from group ${group.id}" + } bot.network.run { val response: TroopManagement.Kick.Response = TroopManagement.Kick( client = bot.client, @@ -206,8 +210,9 @@ internal class MemberImpl constructor( message = message ).sendAndExpect() - check(response.success) { "kick failed: $message" } + check(response.success) { "kick failed: ${response.ret}" } + group.members.delegate.removeIf { it.id == this@MemberImpl.id } MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast() } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt index 166005a13..878127691 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/QQImpl.kt @@ -8,7 +8,7 @@ */ @file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class) -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR") package net.mamoe.mirai.qqandroid.contact diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt index 7b5683240..09908253d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt @@ -28,6 +28,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI +@OptIn(MiraiInternalAPI::class) internal suspend fun QQ.sendMessageImpl(message: Message): MessageReceipt { val event = MessageSendEvent.FriendMessageSendEvent(this, message.asMessageChain()).broadcast() if (event.isCancelled) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt index 27bd5492a..e19224b66 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt @@ -255,6 +255,10 @@ internal class OfflineMessageSourceImplBySourceMsg( // from others' quotation override val bot: Bot, groupIdOrZero: Long ) : OfflineMessageSource(), MessageSourceImpl { + init { + println(delegate._miraiContentToString()) + } + override val kind: Kind get() = if (delegate.srcMsg == null) Kind.GROUP else Kind.FRIEND private val isRecalled: AtomicBoolean = atomic(false) @@ -276,7 +280,7 @@ internal class OfflineMessageSourceImplBySourceMsg( // from others' quotation override val id: Int get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids?.toInt() - ?: error("在读取 OfflineMessageSourceImplBySourceMsg.id 时找不到 origUids, delegate=${delegate._miraiContentToString()}") + ?: 0 // override val sourceMessage: MessageChain get() = delegate.toMessageChain() override val fromId: Long get() = delegate.senderUin diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 760ea55f4..2ada44bd3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -30,6 +30,7 @@ import net.mamoe.mirai.qqandroid.contact.GroupImpl import net.mamoe.mirai.qqandroid.contact.QQImpl import net.mamoe.mirai.qqandroid.contact.singleLine import net.mamoe.mirai.qqandroid.event.PacketReceivedEvent +import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopNum import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl @@ -189,8 +190,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler * Don't use concurrently */ suspend fun reloadFriendList() { - // 不要用 fun, 不要 join declaration, 不要用 val, 编译失败警告 - logger.info("开始加载好友信息") + logger.info { "开始加载好友信息" } var currentFriendCount = 0 var totalFriendCount: Short while (true) { @@ -221,6 +221,58 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler logger.info { "好友列表加载完成, 共 ${currentFriendCount}个" } } + suspend fun StTroopNum.reloadGroup() { + retryCatching(3) { + bot.groups.delegate.addLast( + @Suppress("DuplicatedCode") + GroupImpl( + bot = bot, + coroutineContext = bot.coroutineContext, + id = groupCode, + groupInfo = bot._lowLevelQueryGroupInfo(groupCode).apply { + this as GroupInfoImpl + + if (this.delegate.groupName == null) { + this.delegate.groupName = groupName + } + + if (this.delegate.groupMemo == null) { + this.delegate.groupMemo = groupMemo + } + + if (this.delegate.groupUin == null) { + this.delegate.groupUin = groupUin + } + + this.delegate.groupCode = this@reloadGroup.groupCode + }, + members = bot._lowLevelQueryGroupMemberList( + groupUin, + groupCode, + dwGroupOwnerUin + ) + ) + ) + }.getOrThrow() + } + + suspend fun reloadGroupList() { + logger.info { "开始加载群组列表与群成员列表" } + val troopListData = FriendList.GetTroopListSimplify(bot.client) + .sendAndExpect(retry = 3) + + troopListData.groups.chunked(50).forEach { chunk -> + coroutineScope { + chunk.forEach { + launch { + retryCatching(3) { it.reloadGroup() }.getOrThrow() + } + } + } + } + logger.info { "群组列表与群成员加载完成, 共 ${troopListData.groups.size}个" } + } + @OptIn(MiraiExperimentalAPI::class, ExperimentalTime::class) override suspend fun init(): Unit = coroutineScope { check(bot.isActive) { "bot is dead therefore network can't init" } @@ -236,92 +288,16 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler _pendingEnabled.value = true } - supervisorScope { - this.launch { reloadFriendList() } - - launch { - try { - logger.info("开始加载群组列表与群成员列表") - val troopListData = FriendList.GetTroopListSimplify(bot.client) - .sendAndExpect(retry = 3) - - troopListData.groups.chunked(50).forEach { chunk -> - supervisorScope { - chunk.forEach { troopNum -> - // 别用 fun, 别 val, 编译失败警告 - lateinit var loadGroup: suspend () -> Unit - - loadGroup = suspend { - retryCatching(3) { - bot.groups.delegate.addLast( - @Suppress("DuplicatedCode") - (GroupImpl( - bot = bot, - coroutineContext = bot.coroutineContext, - id = troopNum.groupCode, - groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply { - this as GroupInfoImpl - - if (this.delegate.groupName == null) { - this.delegate.groupName = troopNum.groupName - } - - if (this.delegate.groupMemo == null) { - this.delegate.groupMemo = troopNum.groupMemo - } - - if (this.delegate.groupUin == null) { - this.delegate.groupUin = troopNum.groupUin - } - - this.delegate.groupCode = troopNum.groupCode - }, - members = bot._lowLevelQueryGroupMemberList( - troopNum.groupUin, - troopNum.groupCode, - troopNum.dwGroupOwnerUin - ) - )) - ) - }.exceptionOrNull()?.let { - logger.error { "群${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试" } - logger.error(it) - this@QQAndroidBotNetworkHandler.launch { - delay(10_000) - loadGroup() - } - } - Unit // 别删, 编译失败警告 - } - launch { - loadGroup() - } - } - } - } - logger.info { "群组列表与群成员加载完成, 共 ${troopListData.groups.size}个" } - } catch (e: Exception) { - logger.error { "加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表" } - logger.error(e) - } - } + coroutineScope { + launch { reloadFriendList() } + launch { reloadGroupList() } } - runCatching { - withTimeoutOrNull(30000) { - lateinit var listener: Listener - listener = this.subscribeAlways { - if (it.packet is MessageSvc.PbGetMsg.GetMsgSuccess) { - listener.complete() - } - } + withTimeoutOrNull(30000) { + launch { subscribingGet { Unit } } + MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect() + } ?: error("timeout syncing friend message history") - MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect() - } ?: error("timeout syncing friend message history") - }.exceptionOrNull()?.let { - logger.error("exception while loading syncing friend message history: ${it.message}") - logger.error(it) - } bot.firstLoginSucceed = true _pendingEnabled.value = false @@ -568,19 +544,15 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler check(bot.isActive) { "bot is dead therefore can't send any packet" } check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't send any packet" } logger.verbose("Send: ${this.commandName}") - withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { - PacketLogger.debug { "Channel sending: $commandName" } - channel.send(delegate) - PacketLogger.debug { "Channel send done: $commandName" } - } + channel.send(delegate) } class TimeoutException(override val message: String?) : Exception() /** - * 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms) + * 发送一个包, 挂起协程直到接收到指定的返回包或超时 */ - suspend fun OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 2): E { + suspend fun OutgoingPacket.sendAndExpect(timeoutMillis: Long = 5000, retry: Int = 2): E { require(timeoutMillis > 100) { "timeoutMillis must > 100" } require(retry >= 0) { "retry must >= 0" } @@ -588,19 +560,13 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't send any packet" } suspend fun doSendAndReceive(handler: PacketListener, data: Any, length: Int): E { - withTimeoutOrNull(3000) { - withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { - PacketLogger.debug { "Channel sending: $commandName" } - when (data) { - is ByteArray -> channel.send(data, 0, length) - is ByteReadPacket -> channel.send(data) - else -> error("Internal error: unexpected data type: ${data::class.simpleName}") - } - PacketLogger.debug { "Channel send done: $commandName" } - } - } ?: throw TimeoutException("timeout sending packet $commandName") + when (data) { + is ByteArray -> channel.send(data, 0, length) + is ByteReadPacket -> channel.send(data) + else -> error("Internal error: unexpected data type: ${data::class.simpleName}") + } - logger.verbose("Send done: $commandName") + logger.verbose { "Send done: $commandName" } @Suppress("UNCHECKED_CAST") return withTimeoutOrNull(timeoutMillis) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt index a4d9ef3a0..502b39f97 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt @@ -44,7 +44,7 @@ internal class GroupInfoImpl( override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0 override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0 override val muteAll: Boolean get() = delegate.shutupTimestamp != 0 - override val botMuteRemaining: Int get() = delegate.shutupTimestampMe ?: 0 + override val botMuteTimestamp: Int get() = delegate.shutupTimestampMe ?: 0 } internal class TroopManagement { @@ -146,14 +146,17 @@ internal class TroopManagement { internal object Kick : OutgoingPacketFactory("OidbSvc.0x8a0_0") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val ret = this.readBytes() + .loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x8a0.RspBody.serializer()).msgKickResult!![0].optUint32Result return Response( - this.readBytes() - .loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x8a0.RspBody.serializer()).msgKickResult!![0].optUint32Result == 1 + ret == 0, + ret ) } class Response( - val success: Boolean + val success: Boolean, + val ret: Int ) : Packet { override fun toString(): String = "TroopManagement.Kick.Response($success)" } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index 7b5343739..7fe6ec3d9 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -23,6 +23,7 @@ import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.data.MemberInfo +import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.MemberJoinEvent @@ -116,7 +117,8 @@ internal class MessageSvc { } @OptIn(MiraiInternalAPI::class) - open class GetMsgSuccess(delegate: List) : Response(MsgSvc.SyncFlag.STOP, delegate) { + open class GetMsgSuccess(delegate: List) : Response(MsgSvc.SyncFlag.STOP, delegate), Event, + Packet.NoLog { override fun toString(): String = "MessageSvc.PbGetMsg.GetMsgSuccess(messages=))" } diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt deleted file mode 100644 index d007f69be..000000000 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt +++ /dev/null @@ -1,247 +0,0 @@ -@file:Suppress("unused") - -package net.mamoe.mirai - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.io.ByteReadChannel -import net.mamoe.mirai.contact.* -import net.mamoe.mirai.data.AddFriendResult -import net.mamoe.mirai.event.events.MemberJoinRequestEvent -import net.mamoe.mirai.event.events.NewFriendRequestEvent -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") -@OptIn( - MiraiInternalAPI::class, LowLevelAPI::class, MiraiExperimentalAPI::class, JavaFriendlyAPI::class -) -actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI() { - actual companion object { - /** - * 复制一份此时的 [Bot] 实例列表. - */ - @JvmStatic - actual val instances: List> - 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 - - @PlannedRemoval("1.0.0") - @Deprecated("use id instead", replaceWith = ReplaceWith("id")) - actual abstract val uin: Long - - /** - * QQ 号码. 实际类型为 uint - */ - @SinceMirai("0.32.0") - actual abstract val id: Long - - /** - * 昵称 - */ - actual abstract val nick: String - - /** - * 日志记录器 - */ - actual abstract val logger: MiraiLogger - - // region contacts - - actual abstract val selfQQ: QQ - - /** - * 机器人的好友列表. 它将与服务器同步更新 - */ - actual abstract val friends: ContactList - - /** - * 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException] - */ - actual fun getFriend(id: Long): QQ { - if (id == this.id) return selfQQ - return friends.delegate.getOrNull(id) - ?: throw NoSuchElementException("No such friend $id for bot ${this.id}") - } - - /** - * 机器人加入的群列表. - */ - actual abstract val groups: ContactList - - /** - * 获取一个机器人加入的群. - * - * @throws NoSuchElementException 当不存在这个群时 - */ - actual fun getGroup(id: Long): Group { - return groups.delegate.getOrNull(id) - ?: throw NoSuchElementException("No such group $id for bot ${this.id}") - } - - // endregion - - // region network - - /** - * 网络模块 - */ - actual abstract val network: BotNetworkHandler - - /** - * 挂起协程直到 [Bot] 下线. - */ - @JvmSynthetic - actual suspend inline fun join() = network.join() - - /** - * 登录, 或重新登录. - * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. - * - * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. - * - * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] - * - * @throws LoginFailedException - */ - @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 - */ - @JvmSynthetic - actual abstract suspend fun recall(source: MessageSource) - - /** - * 获取图片下载链接 - */ - @JvmSynthetic - actual abstract suspend fun queryImageUrl(image: Image): String - - /** - * 获取图片下载链接并开始下载. - * - * @see ByteReadChannel.copyAndClose - * @see ByteReadChannel.copyTo - */ - @JvmSynthetic - actual abstract suspend fun openChannel(image: Image): ByteReadChannel - - /** - * 添加一个好友 - * - * @param message 若需要验证请求时的验证消息. - * @param remark 好友备注 - */ - @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?) - - @OptIn(LowLevelAPI::class, MiraiExperimentalAPI::class) - actual final override fun toString(): String = "Bot($id)" - - /** - * 通过好友验证 - * - * @param event 好友验证的事件对象 - */ - @JvmSynthetic - actual abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) - - /** - * 拒绝好友验证 - * - * @param event 好友验证的事件对象 - * @param blackList 拒绝后是否拉入黑名单 - */ - @JvmSynthetic - actual abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean) - - /** - * 通过加群验证(需管理员权限) - * - * @param event 加群验证的事件对象 - */ - @JvmSynthetic - actual abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent) - - /** - * 拒绝加群验证(需管理员权限) - * - * @param event 加群验证的事件对象 - * @param blackList 拒绝后是否拉入黑名单 - */ - @JvmSynthetic - actual abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) - - /** - * 忽略加群验证(需管理员权限) - * - * @param event 加群验证的事件对象 - * @param blackList 忽略后是否拉入黑名单 - */ - @JvmSynthetic - actual abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 5d5c490b8..d7e04694d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -48,24 +48,25 @@ suspend inline fun B.alsoLogin(): B = also { login() } */ @Suppress("INAPPLICABLE_JVM_NAME") @OptIn(MiraiInternalAPI::class, LowLevelAPI::class) -expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { +abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI() { companion object { /** * 复制一份此时的 [Bot] 实例列表. */ @JvmStatic val instances: List> + get() = BotImpl.instances.toList() /** * 遍历每一个 [Bot] 实例 */ - inline fun forEachInstance(block: (Bot) -> Unit) + inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) /** * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] */ @JvmStatic - fun getInstance(qq: Long): Bot + fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq) } /** @@ -105,52 +106,39 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { abstract val selfQQ: QQ /** - * 机器人的好友列表. 它将与服务器同步更新 + * 机器人的好友列表. 与服务器同步更新 */ abstract val friends: ContactList /** - * 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException] + * 获取一个好友对象. + * @throws [NoSuchElementException] 当不存在这个好友时抛出 */ - fun getFriend(id: Long): QQ + fun getFriend(id: Long): QQ = friends.firstOrNull { it.id == id } ?: throw NoSuchElementException("group $id") /** - * 机器人加入的群列表. + * 机器人加入的群列表. 与服务器同步更新 */ abstract val groups: ContactList /** * 获取一个机器人加入的群. - * - * @throws NoSuchElementException 当不存在这个群时 + * @throws NoSuchElementException 当不存在这个群时抛出 */ - fun getGroup(id: Long): Group + fun getGroup(id: Long): Group = groups.firstOrNull { it.id == id } ?: throw NoSuchElementException("group $id") // endregion // region network - /** - * 网络模块 - */ - abstract val network: BotNetworkHandler - - /** - * 挂起协程直到 [Bot] 下线. - */ - @JvmSynthetic - suspend inline fun join() - /** * 登录, 或重新登录. - * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. + * 这个函数总是关闭一切现有网路任务 (但不会关闭其他任务), 然后重新登录并重新缓存好友列表和群列表. * * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. * - * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] - * - * @throws LoginFailedException - * @see alsoLogin + * @throws LoginFailedException 正常登录失败时抛出 + * @see alsoLogin `.apply { login() }` 捷径 */ @JvmSynthetic abstract suspend fun login() @@ -264,9 +252,26 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { abstract fun close(cause: Throwable? = null) @OptIn(LowLevelAPI::class, MiraiExperimentalAPI::class) - final override fun toString(): String + final override fun toString(): String = "Bot($id)" + + /** + * 网络模块. + * 此为内部 API: 它可能在任意时刻被改动. + */ + @MiraiInternalAPI + abstract val network: BotNetworkHandler + + @PlannedRemoval("1.0.0") + @Deprecated("for binary compatibility until 1.0.0", level = DeprecationLevel.HIDDEN) + suspend inline fun Bot.join() = this.coroutineContext[Job]!!.join() } +/** + * 挂起协程直到 [Bot] 下线. + */ +@JvmSynthetic +suspend inline fun Bot.join() = this.coroutineContext[Job]!!.join() + /** * 撤回这条消息. * diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt index eac89ee2b..ef3770417 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt @@ -16,14 +16,17 @@ import kotlin.jvm.JvmName /** - * 只读联系人列表, lock-free 实现 + * 只读联系人列表, 无锁链表实现 * * @see ContactList.asSequence */ @OptIn(MiraiInternalAPI::class) @Suppress("unused") -class ContactList(@MiraiInternalAPI val delegate: LockFreeLinkedList) : Iterable { - operator fun get(id: Long): C = delegate.asSequence().first { it.id == id } +class ContactList(@MiraiInternalAPI("Implementation may change in future release") val delegate: LockFreeLinkedList) : + Iterable { + operator fun get(id: Long): C = + delegate.asSequence().firstOrNull { it.id == id } ?: throw NoSuchElementException("Contact id $id") + fun getOrNull(id: Long): C? = delegate.getOrNull(id) val size: Int get() = delegate.size diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt index c494d2f27..d5c394c33 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt @@ -63,7 +63,7 @@ interface GroupInfo { /** * 机器人被禁言还剩时间, 秒. */ - val botMuteRemaining: Int + val botMuteTimestamp: Int /* /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt index 62d3ce631..5121c12d1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt @@ -356,7 +356,7 @@ data class GroupAllowMemberInviteEvent( data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet /** - * 成员离开群的事件 + * 成员离开群的事件. 在事件广播前成员就已经从 [Group.members] 中删除 */ sealed class MemberLeaveEvent : GroupMemberEvent { /** @@ -365,7 +365,7 @@ sealed class MemberLeaveEvent : GroupMemberEvent { data class Kick( override val member: Member, /** - * 操作人. 为 null 则是机器人操作 + * 操作人. 为 null 则是机器人操作. */ override val operator: Member? ) : MemberLeaveEvent(), Packet, GroupOperableEvent { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt index 50dd37c9f..44ac0ba08 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt @@ -23,6 +23,7 @@ import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.message.ContactMessage import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.GroupMessage +import net.mamoe.mirai.message.TempMessage import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.firstIsInstance import net.mamoe.mirai.utils.SinceMirai @@ -115,6 +116,31 @@ fun CoroutineScope.subscribeFriendMessages( }.run(listeners) } +typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> + +/** + * 订阅来自所有 [Bot] 的所有临时会话消息事件 + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 + */ +@OptIn(ExperimentalContracts::class) +fun CoroutineScope.subscribeTempMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + concurrencyKind: Listener.ConcurrencyKind = Listener.ConcurrencyKind.CONCURRENT, + listeners: TempMessageSubscribersBuilder.() -> R +): R { + contract { + callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) + } + return TempMessageSubscribersBuilder(Unit) { filter, listener -> + subscribeAlways(coroutineContext, concurrencyKind) { + val toString = this.message.contentToString() + if (filter(this, toString)) + listener(this, toString) + } + }.run(listeners) +} + /** * 订阅来自这个 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. * @@ -186,6 +212,31 @@ fun Bot.subscribeFriendMessages( }.run(listeners) } + +/** + * 订阅来自这个 [Bot] 的所有临时会话消息事件. + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 + */ +@SinceMirai("0.35.0") +@OptIn(ExperimentalContracts::class) +fun Bot.subscribeTempMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + concurrencyKind: Listener.ConcurrencyKind = Listener.ConcurrencyKind.CONCURRENT, + listeners: TempMessageSubscribersBuilder.() -> R +): R { + contract { + callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) + } + return TempMessageSubscribersBuilder(Unit) { filter, listener -> + this.subscribeAlways(coroutineContext, concurrencyKind) { + val toString = this.message.contentToString() + if (filter(this, toString)) + listener(this, toString) + } + }.run(listeners) +} + /** * 打开一个指定事件的接收通道 * @@ -582,6 +633,10 @@ open class MessageSubscribersBuilder( @MessageDsl fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage } + /** 如果是好友发来的消息 */ + @MessageDsl + fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessage } + /** 如果是管理员或群主发的消息 */ @MessageDsl fun sentByOperator(): ListeningFilter = diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt index 5cae00617..daff84a10 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt @@ -20,6 +20,7 @@ import net.mamoe.mirai.utils.WeakRef /** * 标示这个 API 是低级的 API. * + * 低级的 API 可能在任意时刻被改动. * 使用低级的 API 无法带来任何安全和便捷保障. * 仅在某些使用结构化 API 可能影响性能的情况下使用这些低级 API. */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt index b9d0a8138..e0075ddd5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt @@ -13,15 +13,24 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.source import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.unsafeWeakRef +/** + * 好友消息 + */ class FriendMessage( sender: QQ, override val message: MessageChain ) : ContactMessage(), BroadcastControllable { + init { + val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") + check(source is OnlineMessageSource.Incoming.FromFriend) { "source provided to a FriendMessage must be an instance of OnlineMessageSource.Incoming.FromFriend" } + } + override val sender: QQ by sender.unsafeWeakRef() override val bot: Bot get() = sender.bot override val subject: QQ get() = sender diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt index 883a25caa..fc961d633 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt @@ -15,6 +15,7 @@ import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.Event import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.source import net.mamoe.mirai.utils.getValue @@ -30,6 +31,11 @@ class GroupMessage( sender: Member, override val message: MessageChain ) : ContactMessage(), Event { + init { + val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") + check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessage must be an instance of OnlineMessageSource.Incoming.FromGroup" } + } + override val sender: Member by sender.unsafeWeakRef() val group: Group get() = sender.group override val bot: Bot get() = sender.bot diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessage.kt index f02688e16..3b4062979 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessage.kt @@ -4,19 +4,31 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Member import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.source +import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.unsafeWeakRef +/** + * 临时会话消息 + */ +@SinceMirai("0.35.0") class TempMessage( sender: Member, override val message: MessageChain ) : ContactMessage(), BroadcastControllable { + init { + val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") + check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a TempMessage must be an instance of OnlineMessageSource.Incoming.FromTemp" } + } + override val sender: Member by sender.unsafeWeakRef() override val bot: Bot get() = sender.bot override val subject: Member get() = sender override val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp - override fun toString(): String = "TempMessage(sender=${sender.id} from group(${sender.group.id}), message=$message)" + override fun toString(): String = + "TempMessage(sender=${sender.id} from group(${sender.group.id}), message=$message)" } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt index 621c0ba8e..fb28ae285 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt @@ -56,6 +56,8 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle subject is Member is OfflineMessageSource -> kind == OfflineMessageSource.Kind.TEMP } @@ -302,6 +304,12 @@ abstract class OfflineMessageSource : MessageSource() { */ abstract val kind: Kind + /** + * 消息 id. + * 服务器不一定提供 id. 因此此值可能为 0 + */ + abstract override val id: Int + // final override fun toString(): String = "OfflineMessageSource(sender=$senderId, target=$targetId)" } diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt deleted file mode 100644 index b74dec434..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt +++ /dev/null @@ -1,255 +0,0 @@ -@file:Suppress("unused") - -package net.mamoe.mirai - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.io.ByteReadChannel -import net.mamoe.mirai.contact.* -import net.mamoe.mirai.data.AddFriendResult -import net.mamoe.mirai.event.events.NewFriendRequestEvent -import net.mamoe.mirai.event.events.MemberJoinRequestEvent -import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.* -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") -@OptIn( - MiraiInternalAPI::class, LowLevelAPI::class, MiraiExperimentalAPI::class, JavaFriendlyAPI::class -) -actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI() { - actual companion object { - /** - * 复制一份此时的 [Bot] 实例列表. - */ - @JvmStatic - actual val instances: List> - get() = BotImpl.instances.toList() - - /** - * 遍历每一个 [Bot] 实例 - */ - @JvmName("forEachInstanceKotlin") - @JvmSynthetic - actual inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) - - /** - * 遍历每一个 [Bot] 实例 - */ - @JavaFriendlyAPI - @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 的默认实现为 [net.mamoe.mirai.utils.Context] - * 在 Android 实现为 `android.content.Context` - */ - actual abstract val context: Context - - @PlannedRemoval("1.0.0") - @Deprecated("use id instead", replaceWith = ReplaceWith("id")) - actual abstract val uin: Long - - /** - * QQ 号码. 实际类型为 uint - */ - @SinceMirai("0.32.0") - actual abstract val id: Long - - /** - * 昵称 - */ - actual abstract val nick: String - - /** - * 日志记录器 - */ - actual abstract val logger: MiraiLogger - - // region contacts - - actual abstract val selfQQ: QQ - - /** - * 机器人的好友列表. 它将与服务器同步更新 - */ - actual abstract val friends: ContactList - - /** - * 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException] - */ - actual fun getFriend(id: Long): QQ { - if (id == this.id) return selfQQ - return friends.delegate.getOrNull(id) - ?: throw NoSuchElementException("No such friend $id for bot ${this.id}") - } - - /** - * 机器人加入的群列表. - */ - actual abstract val groups: ContactList - - /** - * 获取一个机器人加入的群. - * - * @throws NoSuchElementException 当不存在这个群时 - */ - actual fun getGroup(id: Long): Group { - return groups.delegate.getOrNull(id) - ?: throw NoSuchElementException("No such group $id for bot ${this.id}") - } - - // endregion - - // region network - - /** - * 网络模块 - */ - actual abstract val network: BotNetworkHandler - - /** - * 挂起协程直到 [Bot] 下线. - */ - @JvmSynthetic - actual suspend inline fun join() = network.join() - - /** - * 登录, 或重新登录. - * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. - * - * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. - * - * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] - * - * @throws LoginFailedException - */ - @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 - */ - @JvmSynthetic - actual abstract suspend fun recall(source: MessageSource) - - /** - * 获取图片下载链接 - */ - @JvmSynthetic - actual abstract suspend fun queryImageUrl(image: Image): String - - /** - * 获取图片下载链接并开始下载. - * - * @see ByteReadChannel.copyAndClose - * @see ByteReadChannel.copyTo - */ - @JvmSynthetic - actual abstract suspend fun openChannel(image: Image): ByteReadChannel - - /** - * 添加一个好友 - * - * @param message 若需要验证请求时的验证消息. - * @param remark 好友备注 - */ - @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?) - - @OptIn(LowLevelAPI::class, MiraiExperimentalAPI::class) - actual final override fun toString(): String = "Bot($id)" - - /** - * 通过好友验证 - * - * @param event 好友验证的事件对象 - */ - @JvmSynthetic - actual abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) - - /** - * 拒绝好友验证 - * - * @param event 好友验证的事件对象 - * @param blackList 拒绝后是否拉入黑名单 - */ - @JvmSynthetic - actual abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean) - - /** - * 通过加群验证(需管理员权限) - * - * @param event 加群验证的事件对象 - */ - @JvmSynthetic - actual abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent) - - /** - * 拒绝加群验证(需管理员权限) - * - * @param event 加群验证的事件对象 - * @param blackList 拒绝后是否拉入黑名单 - */ - @JvmSynthetic - actual abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) - - /** - * 忽略加群验证(需管理员权限) - * - * @param event 加群验证的事件对象 - * @param blackList 忽略后是否拉入黑名单 - */ - @JvmSynthetic - actual abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) -} \ No newline at end of file