Bot.kt 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /*
  2. * Copyright 2020 Mamoe Technologies and contributors.
  3. *
  4. * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
  5. * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
  6. *
  7. * https://github.com/mamoe/mirai/blob/master/LICENSE
  8. */
  9. @file:Suppress(
  10. "EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE", "UnusedImport",
  11. "EXPERIMENTAL_OVERRIDE"
  12. )
  13. package net.mamoe.mirai
  14. import kotlinx.coroutines.*
  15. import net.mamoe.mirai.contact.*
  16. import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
  17. import net.mamoe.mirai.event.events.MemberJoinRequestEvent
  18. import net.mamoe.mirai.event.events.NewFriendRequestEvent
  19. import net.mamoe.mirai.message.MessageReceipt
  20. import net.mamoe.mirai.message.data.*
  21. import net.mamoe.mirai.network.LoginFailedException
  22. import net.mamoe.mirai.utils.*
  23. import kotlin.coroutines.CoroutineContext
  24. import kotlin.coroutines.EmptyCoroutineContext
  25. import kotlin.jvm.JvmField
  26. import kotlin.jvm.JvmStatic
  27. import kotlin.jvm.JvmSynthetic
  28. /**
  29. * 登录, 返回 [this]
  30. */
  31. @JvmSynthetic
  32. suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
  33. /**
  34. * 机器人对象. 一个机器人实例登录一个 QQ 账号.
  35. * Mirai 为多账号设计, 可同时维护多个机器人.
  36. *
  37. * 有关 [Bot] 生命管理, 请查看 [BotConfiguration.inheritCoroutineContext]
  38. *
  39. * @see Contact 联系人
  40. * @see isActive 判断 [Bot] 是否正常运行中. (协程正常运行) (但不能判断是否在线, 需使用 [isOnline])
  41. *
  42. * @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式.
  43. */
  44. @Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_SUPER_CLASS")
  45. abstract class Bot internal constructor(
  46. val configuration: BotConfiguration
  47. ) : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI, ContactOrBot {
  48. final override val coroutineContext: CoroutineContext = // for id
  49. configuration.parentCoroutineContext
  50. .plus(SupervisorJob(configuration.parentCoroutineContext[Job]))
  51. .plus(configuration.parentCoroutineContext[CoroutineExceptionHandler]
  52. ?: CoroutineExceptionHandler { _, e ->
  53. logger.error("An exception was thrown under a coroutine of Bot", e)
  54. }
  55. )
  56. .plus(CoroutineName("Mirai Bot"))
  57. companion object {
  58. @JvmField
  59. @Suppress("ObjectPropertyName")
  60. internal val _instances: LockFreeLinkedList<WeakRef<Bot>> = LockFreeLinkedList()
  61. @PlannedRemoval("1.2.0")
  62. @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
  63. @JvmStatic
  64. val instances: List<WeakRef<Bot>>
  65. get() = _instances.toList()
  66. /**
  67. * 复制一份此时的 [Bot] 实例列表.
  68. */
  69. @JvmStatic
  70. val botInstances: List<Bot>
  71. get() = _instances.asSequence().mapNotNull { it.get() }.toList()
  72. /**
  73. * 复制一份此时的 [Bot] 实例列表.
  74. */
  75. @SinceMirai("1.1.0")
  76. @JvmStatic
  77. val botInstancesSequence: Sequence<Bot>
  78. get() = _instances.asSequence().mapNotNull { it.get() }
  79. /**
  80. * 遍历每一个 [Bot] 实例
  81. */
  82. @JvmSynthetic
  83. fun forEachInstance(block: (Bot) -> Unit) = _instances.forEach { it.get()?.let(block) }
  84. /**
  85. * 获取一个 [Bot] 实例, 无对应实例时抛出 [NoSuchElementException]
  86. */
  87. @JvmStatic
  88. @Throws(NoSuchElementException::class)
  89. fun getInstance(qq: Long): Bot =
  90. getInstanceOrNull(qq) ?: throw NoSuchElementException(qq.toString())
  91. /**
  92. * 获取一个 [Bot] 实例, 无对应实例时返回 `null`
  93. */
  94. @JvmStatic
  95. fun getInstanceOrNull(qq: Long): Bot? =
  96. _instances.asSequence().mapNotNull { it.get() }.firstOrNull { it.id == qq }
  97. }
  98. init {
  99. _instances.addLast(this.weakRef())
  100. supervisorJob.invokeOnCompletion {
  101. _instances.removeIf { it.get()?.id == this.id }
  102. }
  103. }
  104. /**
  105. * [Bot] 运行的 [Context].
  106. *
  107. * 在 JVM 的默认实现为 `class ContextImpl : Context`
  108. * 在 Android 实现为 `android.content.Context`
  109. */
  110. @MiraiExperimentalAPI
  111. abstract val context: Context
  112. /**
  113. * QQ 号码. 实际类型为 uint
  114. */
  115. abstract override val id: Long
  116. /**
  117. * 昵称
  118. */
  119. abstract val nick: String
  120. /**
  121. * 日志记录器
  122. */
  123. abstract val logger: MiraiLogger
  124. /**
  125. * 判断 Bot 是否在线 (可正常收发消息)
  126. */
  127. @SinceMirai("1.0.1")
  128. abstract val isOnline: Boolean
  129. // region contacts
  130. /**
  131. * [User.id] 与 [Bot.id] 相同的 [_lowLevelNewFriend] 实例
  132. */
  133. @MiraiExperimentalAPI
  134. abstract val selfQQ: Friend
  135. /**
  136. * 机器人的好友列表. 与服务器同步更新
  137. */
  138. abstract val friends: ContactList<Friend>
  139. /**
  140. * 获取一个好友对象.
  141. * @throws [NoSuchElementException] 当不存在这个好友时抛出
  142. */
  143. fun getFriend(id: Long): Friend = friends.firstOrNull { it.id == id } ?: throw NoSuchElementException("friend $id")
  144. /**
  145. * 机器人加入的群列表. 与服务器同步更新
  146. */
  147. abstract val groups: ContactList<Group>
  148. /**
  149. * 获取一个机器人加入的群.
  150. * @throws NoSuchElementException 当不存在这个群时抛出
  151. */
  152. fun getGroup(id: Long): Group = groups.firstOrNull { it.id == id } ?: throw NoSuchElementException("group $id")
  153. // endregion
  154. // region network
  155. /**
  156. * 登录, 或重新登录.
  157. * 这个函数总是关闭一切现有网路任务 (但不会关闭其他任务), 然后重新登录并重新缓存好友列表和群列表.
  158. *
  159. * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况.
  160. *
  161. * @throws LoginFailedException 正常登录失败时抛出
  162. * @see alsoLogin `.apply { login() }` 捷径
  163. */
  164. @JvmSynthetic
  165. abstract suspend fun login()
  166. // endregion
  167. // region actions
  168. /**
  169. * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息.
  170. *
  171. * [Bot] 撤回自己的消息不需要权限.
  172. * [Bot] 撤回群员的消息需要管理员权限.
  173. *
  174. * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得, 或通过 [buildMessageSource] 构建.
  175. *
  176. * @throws PermissionDeniedException 当 [Bot] 无权限操作时抛出
  177. * @throws IllegalStateException 当这条消息已经被撤回时抛出 (仅同步主动操作)
  178. *
  179. * @see Bot.recall (扩展函数) 接受参数 [MessageChain]
  180. * @see MessageSource.recall 撤回消息扩展
  181. */
  182. @JvmSynthetic
  183. abstract suspend fun recall(source: MessageSource)
  184. /**
  185. * 获取图片下载链接
  186. *
  187. * @see Image.queryUrl [Image] 的扩展函数
  188. */
  189. @PlannedRemoval("1.2.0")
  190. @Deprecated(
  191. "use extension.",
  192. replaceWith = ReplaceWith("image.queryUrl()", imports = ["net.mamoe.mirai.message.data.queryUrl"]),
  193. level = DeprecationLevel.ERROR
  194. )
  195. @JvmSynthetic
  196. abstract suspend fun queryImageUrl(image: Image): String
  197. /**
  198. * 构造一个 [OfflineMessageSource]
  199. *
  200. * @param id 即 [MessageSource.id]
  201. * @param internalId 即 [MessageSource.internalId]
  202. *
  203. * @param fromUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算
  204. * @param targetUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算
  205. */
  206. @MiraiExperimentalAPI("This is very experimental and is subject to change.")
  207. abstract fun constructMessageSource(
  208. kind: OfflineMessageSource.Kind,
  209. fromUin: Long, targetUin: Long,
  210. id: Int, time: Int, internalId: Int,
  211. originalMessage: MessageChain
  212. ): OfflineMessageSource
  213. /**
  214. * 通过好友验证
  215. *
  216. * @param event 好友验证的事件对象
  217. */
  218. @PlannedRemoval("1.2.0")
  219. @Deprecated("use member function.", replaceWith = ReplaceWith("event.accept()"), level = DeprecationLevel.ERROR)
  220. @JvmSynthetic
  221. abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent)
  222. /**
  223. * 拒绝好友验证
  224. *
  225. * @param event 好友验证的事件对象
  226. * @param blackList 拒绝后是否拉入黑名单
  227. */
  228. @PlannedRemoval("1.2.0")
  229. @Deprecated(
  230. "use member function.",
  231. replaceWith = ReplaceWith("event.reject(blackList)"),
  232. level = DeprecationLevel.ERROR
  233. )
  234. @JvmSynthetic
  235. abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false)
  236. /**
  237. * 通过加群验证(需管理员权限)
  238. *
  239. * @param event 加群验证的事件对象
  240. */
  241. @PlannedRemoval("1.2.0")
  242. @Deprecated("use member function.", replaceWith = ReplaceWith("event.accept()"), level = DeprecationLevel.ERROR)
  243. @JvmSynthetic
  244. abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent)
  245. /**
  246. * 拒绝加群验证(需管理员权限)
  247. *
  248. * @param event 加群验证的事件对象
  249. * @param blackList 拒绝后是否拉入黑名单
  250. */
  251. @PlannedRemoval("1.2.0")
  252. @Deprecated(
  253. "use member function.",
  254. replaceWith = ReplaceWith("event.reject(blackList)"),
  255. level = DeprecationLevel.HIDDEN
  256. )
  257. abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
  258. @JvmSynthetic
  259. abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false, message: String = "")
  260. /**
  261. * 忽略加群验证(需管理员权限)
  262. *
  263. * @param event 加群验证的事件对象
  264. * @param blackList 忽略后是否拉入黑名单
  265. */
  266. @PlannedRemoval("1.2.0")
  267. @Deprecated(
  268. "use member function.",
  269. replaceWith = ReplaceWith("event.ignore(blackList)"),
  270. level = DeprecationLevel.ERROR
  271. )
  272. @JvmSynthetic
  273. abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
  274. /**
  275. * 接收邀请入群(需管理员权限)
  276. *
  277. * @param event 邀请入群的事件对象
  278. */
  279. @PlannedRemoval("1.2.0")
  280. @Deprecated("use member function.", replaceWith = ReplaceWith("event.accept()"), level = DeprecationLevel.ERROR)
  281. @JvmSynthetic
  282. abstract suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
  283. /**
  284. * 忽略邀请入群(需管理员权限)
  285. *
  286. * @param event 邀请入群的事件对象
  287. */
  288. @PlannedRemoval("1.2.0")
  289. @Deprecated("use member function.", replaceWith = ReplaceWith("event.ignore()"), level = DeprecationLevel.ERROR)
  290. @JvmSynthetic
  291. abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
  292. // endregion
  293. /**
  294. * 关闭这个 [Bot], 立即取消 [Bot] 的 [SupervisorJob].
  295. * 之后 [isActive] 将会返回 `false`.
  296. *
  297. * **注意:** 不可重新登录. 必须重新实例化一个 [Bot].
  298. *
  299. * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭
  300. *
  301. * @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭
  302. */
  303. abstract fun close(cause: Throwable? = null)
  304. final override fun toString(): String = "Bot($id)"
  305. }
  306. /**
  307. * 获取 [Job] 的协程 [Job]. 此 [Job] 为一个 [SupervisorJob]
  308. */
  309. @get:JvmSynthetic
  310. val Bot.supervisorJob: CompletableJob
  311. get() = this.coroutineContext[Job] as CompletableJob
  312. /**
  313. * 挂起协程直到 [Bot] 协程被关闭 ([Bot.close]).
  314. * 即使 [Bot] 离线, 也会等待直到协程关闭.
  315. */
  316. @JvmSynthetic
  317. suspend inline fun Bot.join() = this.coroutineContext[Job]!!.join()
  318. /**
  319. * 撤回这条消息.
  320. *
  321. * [Bot] 撤回自己的消息不需要权限, 但需要在发出后 2 分钟内撤回.
  322. * [Bot] 撤回群员的消息需要管理员权限, 可在任意时间撤回.
  323. *
  324. * @throws PermissionDeniedException 当 [Bot] 无权限操作时
  325. * @see Bot.recall
  326. */
  327. @JvmSynthetic
  328. suspend inline fun Bot.recall(message: MessageChain) =
  329. this.recall(message.source)
  330. /**
  331. * 在一段时间后撤回这个消息源所指代的消息.
  332. *
  333. * @param millis 延迟的时间, 单位为毫秒
  334. * @param coroutineContext 额外的 [CoroutineContext]
  335. * @see recall
  336. */
  337. @JvmSynthetic
  338. inline fun CoroutineScope.recallIn(
  339. source: MessageSource,
  340. millis: Long,
  341. coroutineContext: CoroutineContext = EmptyCoroutineContext
  342. ): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
  343. delay(millis)
  344. source.recall()
  345. }
  346. /**
  347. * 在一段时间后撤回这条消息.
  348. *
  349. * @param millis 延迟的时间, 单位为毫秒
  350. * @param coroutineContext 额外的 [CoroutineContext]
  351. * @see recall
  352. */
  353. @JvmSynthetic
  354. inline fun CoroutineScope.recallIn(
  355. message: MessageChain,
  356. millis: Long,
  357. coroutineContext: CoroutineContext = EmptyCoroutineContext
  358. ): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) {
  359. delay(millis)
  360. message.recall()
  361. }
  362. /**
  363. * 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放.
  364. *
  365. * 注: 不可重新登录. 必须重新实例化一个 [Bot].
  366. *
  367. * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭
  368. */
  369. @JvmSynthetic
  370. suspend inline fun Bot.closeAndJoin(cause: Throwable? = null) {
  371. close(cause)
  372. coroutineContext[Job]?.join()
  373. }
  374. @JvmSynthetic
  375. inline fun Bot.containsFriend(id: Long): Boolean = this.friends.contains(id)
  376. @JvmSynthetic
  377. inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id)
  378. @JvmSynthetic
  379. inline fun Bot.getFriendOrNull(id: Long): Friend? = this.friends.getOrNull(id)
  380. @JvmSynthetic
  381. inline fun Bot.getGroupOrNull(id: Long): Group? = this.groups.getOrNull(id)