mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-24 23:20:09 +08:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
98542546bc
@ -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 为第三方依赖库引入项目
|
||||
|
@ -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()) {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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<QQ> {
|
||||
val event = MessageSendEvent.FriendMessageSendEvent(this, message.asMessageChain()).broadcast()
|
||||
if (event.isCancelled) {
|
||||
|
@ -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
|
||||
|
@ -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<FriendList.GetTroopListSimplify.Response>(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<FriendList.GetTroopListSimplify.Response>(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<PacketReceivedEvent>
|
||||
listener = this.subscribeAlways {
|
||||
if (it.packet is MessageSvc.PbGetMsg.GetMsgSuccess) {
|
||||
listener.complete()
|
||||
}
|
||||
}
|
||||
withTimeoutOrNull(30000) {
|
||||
launch { subscribingGet<MessageSvc.PbGetMsg.GetMsgSuccess, Unit> { Unit } }
|
||||
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
|
||||
} ?: error("timeout syncing friend message history")
|
||||
|
||||
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<Packet>()
|
||||
} ?: 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 <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 2): E {
|
||||
suspend fun <E : Packet> 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) {
|
||||
|
@ -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<Kick.Response>("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)"
|
||||
}
|
||||
|
@ -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<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate) {
|
||||
open class GetMsgSuccess(delegate: List<Packet>) : Response(MsgSvc.SyncFlag.STOP, delegate), Event,
|
||||
Packet.NoLog {
|
||||
override fun toString(): String = "MessageSvc.PbGetMsg.GetMsgSuccess(messages=<Iterable>))"
|
||||
}
|
||||
|
||||
|
@ -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<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
|
||||
|
||||
@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<QQ>
|
||||
|
||||
/**
|
||||
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [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<Group>
|
||||
|
||||
/**
|
||||
* 获取一个机器人加入的群.
|
||||
*
|
||||
* @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)
|
||||
}
|
@ -48,24 +48,25 @@ suspend inline fun <B : Bot> 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<WeakRef<Bot>>
|
||||
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<QQ>
|
||||
|
||||
/**
|
||||
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [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<Group>
|
||||
|
||||
/**
|
||||
* 获取一个机器人加入的群.
|
||||
*
|
||||
* @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()
|
||||
|
||||
/**
|
||||
* 撤回这条消息.
|
||||
*
|
||||
|
@ -16,14 +16,17 @@ import kotlin.jvm.JvmName
|
||||
|
||||
|
||||
/**
|
||||
* 只读联系人列表, lock-free 实现
|
||||
* 只读联系人列表, 无锁链表实现
|
||||
*
|
||||
* @see ContactList.asSequence
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Suppress("unused")
|
||||
class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedList<C>) : Iterable<C> {
|
||||
operator fun get(id: Long): C = delegate.asSequence().first { it.id == id }
|
||||
class ContactList<C : Contact>(@MiraiInternalAPI("Implementation may change in future release") val delegate: LockFreeLinkedList<C>) :
|
||||
Iterable<C> {
|
||||
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
|
||||
|
@ -63,7 +63,7 @@ interface GroupInfo {
|
||||
/**
|
||||
* 机器人被禁言还剩时间, 秒.
|
||||
*/
|
||||
val botMuteRemaining: Int
|
||||
val botMuteTimestamp: Int
|
||||
|
||||
/*
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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 <R> CoroutineScope.subscribeFriendMessages(
|
||||
}.run(listeners)
|
||||
}
|
||||
|
||||
typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder<TempMessage, Listener<TempMessage>, Unit, Unit>
|
||||
|
||||
/**
|
||||
* 订阅来自所有 [Bot] 的所有临时会话消息事件
|
||||
*
|
||||
* @see CoroutineScope.incoming 打开一个指定事件的接收通道
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun <R> 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 <R> Bot.subscribeFriendMessages(
|
||||
}.run(listeners)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 订阅来自这个 [Bot] 的所有临时会话消息事件.
|
||||
*
|
||||
* @see CoroutineScope.incoming 打开一个指定事件的接收通道
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun <R> 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<M : ContactMessage, out Ret, R : RR, RR>(
|
||||
@MessageDsl
|
||||
fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage }
|
||||
|
||||
/** 如果是好友发来的消息 */
|
||||
@MessageDsl
|
||||
fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessage }
|
||||
|
||||
/** 如果是管理员或群主发的消息 */
|
||||
@MessageDsl
|
||||
fun sentByOperator(): ListeningFilter =
|
||||
|
@ -20,6 +20,7 @@ import net.mamoe.mirai.utils.WeakRef
|
||||
/**
|
||||
* 标示这个 API 是低级的 API.
|
||||
*
|
||||
* 低级的 API 可能在任意时刻被改动.
|
||||
* 使用低级的 API 无法带来任何安全和便捷保障.
|
||||
* 仅在某些使用结构化 API 可能影响性能的情况下使用这些低级 API.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)"
|
||||
}
|
@ -56,6 +56,8 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<OnlineMes
|
||||
|
||||
/**
|
||||
* 消息 id.
|
||||
* 当 [OnlineMessageSource] 时为随机数.
|
||||
* 当 [OfflineMessageSource] 时可能为 0, 取决于服务器是否提供这个值.
|
||||
*/
|
||||
abstract val id: Int // random
|
||||
|
||||
@ -235,7 +237,7 @@ inline fun MessageSource.isAboutGroup(): Boolean {
|
||||
}
|
||||
|
||||
inline fun MessageSource.isAboutTemp(): Boolean {
|
||||
return when(this) {
|
||||
return when (this) {
|
||||
is OnlineMessageSource -> 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)"
|
||||
}
|
||||
|
||||
|
@ -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<WeakRef<Bot>>
|
||||
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<QQ>
|
||||
|
||||
/**
|
||||
* 获取一个好友对象. 若没有这个好友, 则会抛出异常 [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<Group>
|
||||
|
||||
/**
|
||||
* 获取一个机器人加入的群.
|
||||
*
|
||||
* @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)
|
||||
}
|
Loading…
Reference in New Issue
Block a user