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