Commit 98542546 authored by ryoii's avatar ryoii

Merge remote-tracking branch 'origin/master'

parents 56c63723 aa195b0c
...@@ -51,6 +51,7 @@ Mirai 源码完全开放, 您可以参考 Mirai 的协议实现来开发其他 ...@@ -51,6 +51,7 @@ Mirai 源码完全开放, 您可以参考 Mirai 的协议实现来开发其他
- (社区)`JavaScript`(`Node.js`): [node-mirai](https://github.com/RedBeanN/node-mirai) mirai 的 Node.js SDK - (社区)`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 - (社区)`Go`: [gomirai](https://github.com/Logiase/gomirai) 基于 mirai-api-http 的 GoLang SDK
- (社区)`Mozilla Rhino`: [mirai-rhinojs-sdk](https://github.com/StageGuard/mirai-rhinojs-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-api-http](https://github.com/mamoe/mirai-api-http) 提供的 http 接口进行接入
#### 使用 mirai 为第三方依赖库引入项目 #### 使用 mirai 为第三方依赖库引入项目
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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) @file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
package net.mamoe.mirai.qqandroid.contact package net.mamoe.mirai.qqandroid.contact
...@@ -93,7 +93,7 @@ internal class GroupImpl( ...@@ -93,7 +93,7 @@ internal class GroupImpl(
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override lateinit var botPermission: MemberPermission override lateinit var botPermission: MemberPermission
var _botMuteTimestamp: Int = groupInfo.botMuteRemaining var _botMuteTimestamp: Int = groupInfo.botMuteTimestamp
override val botMuteRemaining: Int = override val botMuteRemaining: Int =
if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) { if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) {
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 package net.mamoe.mirai.qqandroid.contact
...@@ -196,9 +196,13 @@ internal class MemberImpl constructor( ...@@ -196,9 +196,13 @@ internal class MemberImpl constructor(
net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast() net.mamoe.mirai.event.events.MemberUnmuteEvent(this@MemberImpl, null).broadcast()
} }
@OptIn(MiraiInternalAPI::class)
@JvmSynthetic @JvmSynthetic
override suspend fun kick(message: String) { override suspend fun kick(message: String) {
checkBotPermissionHigherThanThis() checkBotPermissionHigherThanThis()
check(group.members.getOrNull(this.id) != null) {
"Member ${this.id} had already been kicked from group ${group.id}"
}
bot.network.run { bot.network.run {
val response: TroopManagement.Kick.Response = TroopManagement.Kick( val response: TroopManagement.Kick.Response = TroopManagement.Kick(
client = bot.client, client = bot.client,
...@@ -206,8 +210,9 @@ internal class MemberImpl constructor( ...@@ -206,8 +210,9 @@ internal class MemberImpl constructor(
message = message message = message
).sendAndExpect() ).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() MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast()
} }
} }
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
*/ */
@file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class) @file:OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR")
package net.mamoe.mirai.qqandroid.contact package net.mamoe.mirai.qqandroid.contact
......
...@@ -28,6 +28,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc ...@@ -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.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
@OptIn(MiraiInternalAPI::class)
internal suspend fun QQ.sendMessageImpl(message: Message): MessageReceipt<QQ> { internal suspend fun QQ.sendMessageImpl(message: Message): MessageReceipt<QQ> {
val event = MessageSendEvent.FriendMessageSendEvent(this, message.asMessageChain()).broadcast() val event = MessageSendEvent.FriendMessageSendEvent(this, message.asMessageChain()).broadcast()
if (event.isCancelled) { if (event.isCancelled) {
......
...@@ -255,6 +255,10 @@ internal class OfflineMessageSourceImplBySourceMsg( // from others' quotation ...@@ -255,6 +255,10 @@ internal class OfflineMessageSourceImplBySourceMsg( // from others' quotation
override val bot: Bot, override val bot: Bot,
groupIdOrZero: Long groupIdOrZero: Long
) : OfflineMessageSource(), MessageSourceImpl { ) : OfflineMessageSource(), MessageSourceImpl {
init {
println(delegate._miraiContentToString())
}
override val kind: Kind get() = if (delegate.srcMsg == null) Kind.GROUP else Kind.FRIEND override val kind: Kind get() = if (delegate.srcMsg == null) Kind.GROUP else Kind.FRIEND
private val isRecalled: AtomicBoolean = atomic(false) private val isRecalled: AtomicBoolean = atomic(false)
...@@ -276,7 +280,7 @@ internal class OfflineMessageSourceImplBySourceMsg( // from others' quotation ...@@ -276,7 +280,7 @@ internal class OfflineMessageSourceImplBySourceMsg( // from others' quotation
override val id: Int override val id: Int
get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids?.toInt() 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 sourceMessage: MessageChain get() = delegate.toMessageChain()
override val fromId: Long get() = delegate.senderUin override val fromId: Long get() = delegate.senderUin
......
...@@ -44,7 +44,7 @@ internal class GroupInfoImpl( ...@@ -44,7 +44,7 @@ internal class GroupInfoImpl(
override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0 override val autoApprove get() = delegate.groupFlagext3?.and(0x00100000) == 0
override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0 override val confessTalk get() = delegate.groupFlagext3?.and(0x00002000) == 0
override val muteAll: Boolean get() = delegate.shutupTimestamp != 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 { internal class TroopManagement {
...@@ -146,14 +146,17 @@ internal class TroopManagement { ...@@ -146,14 +146,17 @@ internal class TroopManagement {
internal object Kick : OutgoingPacketFactory<Kick.Response>("OidbSvc.0x8a0_0") { internal object Kick : OutgoingPacketFactory<Kick.Response>("OidbSvc.0x8a0_0") {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { 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( return Response(
this.readBytes() ret == 0,
.loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x8a0.RspBody.serializer()).msgKickResult!![0].optUint32Result == 1 ret
) )
} }
class Response( class Response(
val success: Boolean val success: Boolean,
val ret: Int
) : Packet { ) : Packet {
override fun toString(): String = "TroopManagement.Kick.Response($success)" override fun toString(): String = "TroopManagement.Kick.Response($success)"
} }
......
...@@ -23,6 +23,7 @@ import net.mamoe.mirai.contact.Member ...@@ -23,6 +23,7 @@ import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.data.MemberInfo 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.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.event.events.MemberJoinEvent
...@@ -116,7 +117,8 @@ internal class MessageSvc { ...@@ -116,7 +117,8 @@ internal class MessageSvc {
} }
@OptIn(MiraiInternalAPI::class) @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>))" override fun toString(): String = "MessageSvc.PbGetMsg.GetMsgSuccess(messages=<Iterable>))"
} }
......
@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)
}
\ No newline at end of file
...@@ -48,24 +48,25 @@ suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() } ...@@ -48,24 +48,25 @@ suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
*/ */
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class) @OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI() {
companion object { companion object {
/** /**
* 复制一份此时的 [Bot] 实例列表. * 复制一份此时的 [Bot] 实例列表.
*/ */
@JvmStatic @JvmStatic
val instances: List<WeakRef<Bot>> val instances: List<WeakRef<Bot>>
get() = BotImpl.instances.toList()
/** /**
* 遍历每一个 [Bot] 实例 * 遍历每一个 [Bot] 实例
*/ */
inline fun forEachInstance(block: (Bot) -> Unit) inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
/** /**
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
*/ */
@JvmStatic @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 { ...@@ -105,52 +106,39 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
abstract val selfQQ: QQ abstract val selfQQ: QQ
/** /**
* 机器人的好友列表. 它将与服务器同步更新 * 机器人的好友列表. 与服务器同步更新
*/ */
abstract val friends: ContactList<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> 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 // endregion
// region network // region network
/**
* 网络模块
*/
abstract val network: BotNetworkHandler
/**
* 挂起协程直到 [Bot] 下线.
*/
@JvmSynthetic
suspend inline fun join()
/** /**
* 登录, 或重新登录. * 登录, 或重新登录.
* 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. * 这个函数总是关闭一切现有网路任务 (但不会关闭其他任务), 然后重新登录并重新缓存好友列表和群列表.
* *
* 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况.
* *
* 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] * @throws LoginFailedException 正常登录失败时抛出
* * @see alsoLogin `.apply { login() }` 捷径
* @throws LoginFailedException
* @see alsoLogin
*/ */
@JvmSynthetic @JvmSynthetic
abstract suspend fun login() abstract suspend fun login()
...@@ -264,9 +252,26 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { ...@@ -264,9 +252,26 @@ expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor {
abstract fun close(cause: Throwable? = null) abstract fun close(cause: Throwable? = null)
@OptIn(LowLevelAPI::class, MiraiExperimentalAPI::class) @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 ...@@ -16,14 +16,17 @@ import kotlin.jvm.JvmName
/** /**
* 只读联系人列表, lock-free 实现 * 只读联系人列表, 无锁链表实现
* *
* @see ContactList.asSequence * @see ContactList.asSequence
*/ */
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@Suppress("unused") @Suppress("unused")
class ContactList<C : Contact>(@MiraiInternalAPI val delegate: LockFreeLinkedList<C>) : Iterable<C> { class ContactList<C : Contact>(@MiraiInternalAPI("Implementation may change in future release") val delegate: LockFreeLinkedList<C>) :
operator fun get(id: Long): C = delegate.asSequence().first { it.id == id } 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) fun getOrNull(id: Long): C? = delegate.getOrNull(id)
val size: Int get() = delegate.size val size: Int get() = delegate.size
......
...@@ -63,7 +63,7 @@ interface GroupInfo { ...@@ -63,7 +63,7 @@ interface GroupInfo {
/** /**
* 机器人被禁言还剩时间, 秒. * 机器人被禁言还剩时间, 秒.
*/ */
val botMuteRemaining: Int val botMuteTimestamp: Int
/* /*
/** /**
......
...@@ -356,7 +356,7 @@ data class GroupAllowMemberInviteEvent( ...@@ -356,7 +356,7 @@ data class GroupAllowMemberInviteEvent(
data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet data class MemberJoinEvent(override val member: Member) : GroupMemberEvent, BotPassiveEvent, Packet
/** /**
* 成员离开群的事件 * 成员离开群的事件. 在事件广播前成员就已经从 [Group.members] 中删除
*/ */
sealed class MemberLeaveEvent : GroupMemberEvent { sealed class MemberLeaveEvent : GroupMemberEvent {
/** /**
...@@ -365,7 +365,7 @@ sealed class MemberLeaveEvent : GroupMemberEvent { ...@@ -365,7 +365,7 @@ sealed class MemberLeaveEvent : GroupMemberEvent {
data class Kick( data class Kick(
override val member: Member, override val member: Member,
/** /**
* 操作人. 为 null 则是机器人操作 * 操作人. 为 null 则是机器人操作.
*/ */
override val operator: Member? override val operator: Member?
) : MemberLeaveEvent(), Packet, GroupOperableEvent { ) : MemberLeaveEvent(), Packet, GroupOperableEvent {
......
...@@ -23,6 +23,7 @@ import net.mamoe.mirai.event.events.BotEvent ...@@ -23,6 +23,7 @@ import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.ContactMessage import net.mamoe.mirai.message.ContactMessage
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage 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.Message
import net.mamoe.mirai.message.data.firstIsInstance import net.mamoe.mirai.message.data.firstIsInstance
import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.SinceMirai
...@@ -115,6 +116,31 @@ fun <R> CoroutineScope.subscribeFriendMessages( ...@@ -115,6 +116,31 @@ fun <R> CoroutineScope.subscribeFriendMessages(
}.run(listeners) }.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] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. * 订阅来自这个 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
* *
...@@ -186,6 +212,31 @@ fun <R> Bot.subscribeFriendMessages( ...@@ -186,6 +212,31 @@ fun <R> Bot.subscribeFriendMessages(
}.run(listeners) }.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>( ...@@ -582,6 +633,10 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
@MessageDsl @MessageDsl
fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage } fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage }
/** 如果是好友发来的消息 */
@MessageDsl
fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessage }
/** 如果是管理员或群主发的消息 */ /** 如果是管理员或群主发的消息 */
@MessageDsl @MessageDsl
fun sentByOperator(): ListeningFilter = fun sentByOperator(): ListeningFilter =
......
...@@ -20,6 +20,7 @@ import net.mamoe.mirai.utils.WeakRef ...@@ -20,6 +20,7 @@ import net.mamoe.mirai.utils.WeakRef
/** /**
* 标示这个 API 是低级的 API. * 标示这个 API 是低级的 API.
* *
* 低级的 API 可能在任意时刻被改动.
* 使用低级的 API 无法带来任何安全和便捷保障. * 使用低级的 API 无法带来任何安全和便捷保障.
* 仅在某些使用结构化 API 可能影响性能的情况下使用这些低级 API. * 仅在某些使用结构化 API 可能影响性能的情况下使用这些低级 API.
*/ */
......
...@@ -13,15 +13,24 @@ import net.mamoe.mirai.Bot ...@@ -13,15 +13,24 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.message.data.MessageChain 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.OnlineMessageSource
import net.mamoe.mirai.message.data.source import net.mamoe.mirai.message.data.source
import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
/**
* 好友消息
*/
class FriendMessage( class FriendMessage(
sender: QQ, sender: QQ,
override val message: MessageChain override val message: MessageChain
) : ContactMessage(), BroadcastControllable { ) : 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 sender: QQ by sender.unsafeWeakRef()
override val bot: Bot get() = sender.bot override val bot: Bot get() = sender.bot
override val subject: QQ get() = sender override val subject: QQ get() = sender
......
...@@ -15,6 +15,7 @@ import net.mamoe.mirai.contact.Member ...@@ -15,6 +15,7 @@ import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
import net.mamoe.mirai.message.data.MessageChain 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.OnlineMessageSource
import net.mamoe.mirai.message.data.source import net.mamoe.mirai.message.data.source
import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.getValue
...@@ -30,6 +31,11 @@ class GroupMessage( ...@@ -30,6 +31,11 @@ class GroupMessage(
sender: Member, sender: Member,
override val message: MessageChain override val message: MessageChain
) : ContactMessage(), Event { ) : 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() override val sender: Member by sender.unsafeWeakRef()
val group: Group get() = sender.group val group: Group get() = sender.group
override val bot: Bot get() = sender.bot override val bot: Bot get() = sender.bot
......
...@@ -4,19 +4,31 @@ import net.mamoe.mirai.Bot ...@@ -4,19 +4,31 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.message.data.MessageChain 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.OnlineMessageSource
import net.mamoe.mirai.message.data.source import net.mamoe.mirai.message.data.source
import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
/**
* 临时会话消息
*/
@SinceMirai("0.35.0")
class TempMessage( class TempMessage(
sender: Member, sender: Member,
override val message: MessageChain override val message: MessageChain
) : ContactMessage(), BroadcastControllable { ) : 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 sender: Member by sender.unsafeWeakRef()
override val bot: Bot get() = sender.bot override val bot: Bot get() = sender.bot
override val subject: Member get() = sender override val subject: Member get() = sender
override val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp 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
...@@ -56,6 +56,8 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<OnlineMes ...@@ -56,6 +56,8 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<OnlineMes
/** /**
* 消息 id. * 消息 id.
* 当 [OnlineMessageSource] 时为随机数.
* 当 [OfflineMessageSource] 时可能为 0, 取决于服务器是否提供这个值.
*/ */
abstract val id: Int // random abstract val id: Int // random
...@@ -235,7 +237,7 @@ inline fun MessageSource.isAboutGroup(): Boolean { ...@@ -235,7 +237,7 @@ inline fun MessageSource.isAboutGroup(): Boolean {
} }
inline fun MessageSource.isAboutTemp(): Boolean { inline fun MessageSource.isAboutTemp(): Boolean {
return when(this) { return when (this) {
is OnlineMessageSource -> subject is Member is OnlineMessageSource -> subject is Member
is OfflineMessageSource -> kind == OfflineMessageSource.Kind.TEMP is OfflineMessageSource -> kind == OfflineMessageSource.Kind.TEMP
} }
...@@ -302,6 +304,12 @@ abstract class OfflineMessageSource : MessageSource() { ...@@ -302,6 +304,12 @@ abstract class OfflineMessageSource : MessageSource() {
*/ */
abstract val kind: Kind abstract val kind: Kind
/**
* 消息 id.
* 服务器不一定提供 id. 因此此值可能为 0
*/
abstract override val id: Int
// final override fun toString(): String = "OfflineMessageSource(sender=$senderId, target=$targetId)" // final override fun toString(): String = "OfflineMessageSource(sender=$senderId, target=$targetId)"
} }
......
@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)
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment