Commit 17af6861 authored by jiahua.liu's avatar jiahua.liu

Merge remote-tracking branch 'origin/master'

parents 4bb9bc6d c8f7d53e
...@@ -2,18 +2,33 @@ ...@@ -2,18 +2,33 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `0.14.0` 2020/2/13
### mirai-core
- **支持 at 全体成员: `AtAll`**
### mirai-core-qqandroid
- **支持 `AtAll` 的发送和解析**
- **修复某些情况下禁言处理异常**
小优化:
-`GroupMessage` 添加 `quoteReply(Message)`, 可快速引用消息并回复
-`CoroutineScope.subscribeMessages` 添加返回值. 返回 lambda 的返回值
- 在验证码无法处理时记录更多信息
- 优化 `At` 的空格处理 (自动为 `At` 之后的消息添加空格)
- 删除 `BotConfiguration` 中一些过时的设置
## `0.13.0` 2020/2/12 ## `0.13.0` 2020/2/12
### mirai-core ### mirai-core
1. 修改 BotFactory, 添加 `context` 参数. - 修改 BotFactory, 添加 `context` 参数.
2. currentTimeMillis 减少不必要对象创建 - currentTimeMillis 减少不必要对象创建
3. 优化无锁链表性能 (大幅提升 `addAll` 性能) - 优化无锁链表性能 (大幅提升 `addAll` 性能)
### mirai-core-qqanroid ### mirai-core-qqanroid
安卓协议发布, 基于最新 QQ, 版本 `8.2.0` 安卓协议发布, 基于最新 QQ, 版本 `8.2.0`
支持的功能: 支持的功能:
- 登录: 密码登录. 设备锁支持, 不安全状态支持, 图片验证码支持, 滑动验证码支持. - 登录: 密码登录. 设备锁支持, 不安全状态支持, 图片验证码支持, 滑动验证码支持.
- 消息: 文字消息, 图片消息(含表情消息), 群员 At. - 消息: 文字消息, 图片消息(含表情消息), 群员 At, 引用回复.
- 列表: 群列表, 群员列表, 好友列表均已稳定. - 列表: 群列表, 群员列表, 好友列表均已稳定.
- 群操作: 查看和修改群名, 查看和修改群属性(含全体禁言, 坦白说, 自动批准加入, 匿名聊天, 允许成员拉人), 设置和解除成员禁言, 查看和修改成员名片, 踢出成员. - 群操作: 查看和修改群名, 查看和修改群属性(含全体禁言, 坦白说, 自动批准加入, 匿名聊天, 允许成员拉人), 设置和解除成员禁言, 查看和修改成员名片, 踢出成员.
- 消息事件: 接受群消息和好友消息并解析 - 消息事件: 接受群消息和好友消息并解析
......
...@@ -53,7 +53,7 @@ repositories{ ...@@ -53,7 +53,7 @@ repositories{
若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。 若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。
**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 JVM 运行则只需要`-jvm`的依赖** **若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 JVM 运行则只需要`-jvm`的依赖**
请将 `VERSION` 替换为最新的版本(如 `0.10.6`): 请将 `VERSION` 替换为最新的版本(如 `0.13.0`):
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
**Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.** **Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.**
...@@ -64,15 +64,15 @@ Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。 ...@@ -64,15 +64,15 @@ Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。
**common** **common**
```kotlin ```kotlin
implementation("net.mamoe:mirai-core-timpc-common:VERSION") implementation("net.mamoe:mirai-core-qqandroid-common:VERSION")
``` ```
**jvm** **jvm**
```kotlin ```kotlin
implementation("net.mamoe:mirai-core-timpc-jvm:VERSION") implementation("net.mamoe:mirai-core-qqandroid-jvm:VERSION")
``` ```
**android** **android**
```kotlin ```kotlin
implementation("net.mamoe:mirai-core-timpc-android:VERSION") implementation("net.mamoe:mirai-core-qqandroid-android:VERSION")
``` ```
### Performance ### Performance
Android 上, Mirai 运行需使用 80M 内存. Android 上, Mirai 运行需使用 80M 内存.
......
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
mirai_version=0.13.0 mirai_version=0.14.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin
......
...@@ -13,6 +13,7 @@ import kotlinx.io.core.readUInt ...@@ -13,6 +13,7 @@ import kotlinx.io.core.readUInt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
...@@ -184,6 +185,14 @@ notOnlineImage=NotOnlineImage#2050019814 { ...@@ -184,6 +185,14 @@ notOnlineImage=NotOnlineImage#2050019814 {
pbReserve=08 01 10 00 32 00 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05 pbReserve=08 01 10 00 32 00 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05
} }
*/ */
private val atAllData = ImMsgBody.Elem(
text = ImMsgBody.Text(
str = "@全体成员",
attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes()
)
)
internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> { internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
val elements = mutableListOf<ImMsgBody.Elem>() val elements = mutableListOf<ImMsgBody.Elem>()
...@@ -203,6 +212,7 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> { ...@@ -203,6 +212,7 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
is NotOnlineImageFromFile -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData())) is NotOnlineImageFromFile -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
is AtAll -> elements.add(atAllData)
is QuoteReply, is QuoteReply,
is MessageSource -> { is MessageSource -> {
...@@ -295,7 +305,7 @@ internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain { ...@@ -295,7 +305,7 @@ internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
} }
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class) @UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) { internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) {
this.forEach { this.forEach {
when { when {
...@@ -306,9 +316,18 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) { ...@@ -306,9 +316,18 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) {
if (it.text.attr6Buf.isEmpty()) { if (it.text.attr6Buf.isEmpty()) {
message.add(it.text.str.toMessage()) message.add(it.text.str.toMessage())
} else { } else {
//00 01 00 00 00 0A 00 3E 03 3F A2 00 00 //00 01 00 00 00 05 01 00 00 00 00 00 00 all
val id = it.text.attr6Buf.read { discardExact(7); readUInt().toLong() } //00 01 00 00 00 0A 00 3E 03 3F A2 00 00 one
message.add(At(id, it.text.str)) val id: Long
it.text.attr6Buf.read {
discardExact(7)
id = readUInt().toLong()
}
if (id == 0L){
message.add(AtAll)
} else {
message.add(At(id, it.text.str))
}
} }
} }
} }
......
...@@ -175,11 +175,7 @@ internal object KnownPacketFactories { ...@@ -175,11 +175,7 @@ internal object KnownPacketFactories {
PacketLogger.verbose { "开始处理一个包" } PacketLogger.verbose { "开始处理一个包" }
PacketLogger.verbose { "flag1(0A/0B) = ${flag1.toUByte().toUHexString()}" } PacketLogger.verbose { "flag1(0A/0B) = ${flag1.toUByte().toUHexString()}" }
// 00 00 05 30
// 00 00 00 0A // flag 1
// 01 // packet type. 02: sso, 01: uni
//
// 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 40 3C 63 DC A2 8F FC E7 09 66 62 11 A3 5A B6 AB DC 6E A1 CA CF E2 0A 6F A8 6D 36 64 4E 22 4B A9 8A ED 07 7A 0A 9E F3 C7 7B 72 EF C1 C7 6E 9A 28 27 10 F8 2A 7F 37 49 B6 48 35 52 E9 CF 2A B5 F3 26 90 33 68 9B 5A 04 2A 8B F5 78 13 82 FE 3C 13 C4 F9 38 39 0E 02 4C 3D 91 0A 2A 94 3F 9F A6 52 B9 14 89 C5 D9 57 0F 96 F8 0E 7D 32 81 8E 10 DB C0 CA BE C7 3F EC D0 B1 F0 9D A2 4B 9F B3 8D E0 EB 1F 42 52 EA 5E 9E 76 E2 F4 13 9D 0E 7E 6D 0A E3 56 C3 EE 8A 80 24 DE FB 08 82 FB B7 AF CE 2A 69 16 E3 C3 79 5C C7 CD 44 BA AA 08 A2 51 0B 43 31 69 A1 12 D1 AE 48 15 AE 76 E9 AB BB D2 E0 16 03 EB 2D 47 A4 61 24 65 5E CC C5 03 B3 96 3E 7A 39 90 3D DB 63 56 2B 23 85 CE 5F 9E 04 20 45 31 79 7B BF 78 33 77 34 C1 8E 83 B3 50 88 2A 01 C0 C4 E4 BF 2D 0D B9 37 32 AB E0 BB 82 36 B1 4E 51 4B F7 07 6A 12 3E 79 EA 93 3D BD 06 4E AE 1C 49 82 17 14 00 09 59 40 A6 A9 01 56 1A 23 86 A8 33 B3 9A 70 7B 3A C1 F9 31 03 FD DB 4B 5C 7B F9 BB 43 94 65 A0 1C DA 2B 85 AA AD 7B 79 42 F2 EB 25 5E F0 DA B7 E7 AD 4B 25 02 36 BB 78 5F 83 7F F7 78 F0 99 D2 B5 A3 0C 4A 7F 0E B0 A6 C4 99 F7 9E 0B C6 4D FC F5 8D 6B 5F 35 27 36 D3 DB D0 46 C7 10 76 7D 96 91 48 EA 1C B2 B7 D7 2F D2 88 A8 4C 87 D6 A9 40 33 4C 76 C5 48 3E 32 4D C1 C3 7F 5C D9 B3 22 00 88 BE 04 82 64 A9 73 AA E1 65 1A EF 49 B4 54 74 53 FF 75 B6 E9 57 1B 89 2D 6F 2A 6A CE 23 BF 41 CB 55 B3 A0 53 87 AD A0 22 EE 6B 3F 4A 97 23 36 BF 7E 08 2D 0A 9E 2E 4B F2 2E 00 59 EC F1 21 34 45 75 DB 6B F2 EC 65 24 30 69 50 CC 45 78 00 AF C8 F6 3D 8E 03 60 CF CA A1 88 14 18 82 6F 56 58 D0 BC E0 48 FD AA 86 63 CA C1 01 63 07 16 4A 79 79 17 9D 1F E2 40 4B B6 77 6E 44 84 DE BE 02 4C 33 7A F5 2F 93 21 3E 17 62 38 81 95 E6 84 8B 7C C8 7B E2 23 FB 12 4F E8 42 5F 1D 48 92 84 B1 45 FF 69 97 3C 30 C9 09 E8 84 E8 07 0E 17 D4 A1 CC 35 D6 FE 7B D2 9A 44 8B 17 BF E7 D6 98 1D 98 D7 30 BE 55 19 A9 F4 D6 0D E8 18 80 35 85 B6 AB B9 20 32 C7 ED C6 AD A7 AE 19 48 B7 17 02 B3 45 C3 A2 B9 C9 B7 58 B5 8B 4C AF 52 AD A1 E1 62 45 AB 58 26 67 20 C7 64 AA DA 7E F3 70 8B C2 92 69 E3 3E 3E 6F 39 6F 2B 35 35 0F 00 FC 52 B5 5C 5B 73 FE F6 F5 10 55 36 7C 9A 84 FC A6 23 29 4A 75 49 7C 13 1C CA 54 A2 A2 FA 2A 63 A5 4C 9A B4 27 E8 5F 9F 23 96 B2 E7 AA E6 8B E0 E2 6A 75 8A B2 F4 E4 7E 09 E8 22 70 2A 42 8B E3 DC AD E8 A8 A2 92 71 6B A2 12 78 E1 DA CC 70 57 67 F5 B4 52 F3 B4 4C 17 AB 05 33 DA 6E 47 52 C5 B2 B7 9A D2 A8 BC 44 64 D3 26 1A 6B C6 C5 36 1C 2B 8F BD B7 27 91 3E C0 C2 FC 03 41 FE 02 D3 4B B1 E5 5F 5B 50 05 29 BD 3A 64 85 E3 8C FB 11 F2 1D 94 DB D7 78 AF AD 77 A3 9C D4 39 5D 8B EA DF 9D 08 CA 92 7C 5F D5 17 49 0E FA A1 21 1C 9F C3 88 1A DC E7 D8 82 80 85 86 32 99 15 E4 89 BA 91 2B 4B FB 87 EC 44 B4 D9 83 CC 79 77 A4 A0 D0 50 E3 4F 00 E7 DA DA 79 38 1E D8 04 86 16 CD 25 BE BA 76 E4 8C F9 86 91 69 6E C7 A0 EF 6B 44 2B C9 C3 DC 8D 2D 65 60 7A F4 37 02 D4 8F 38 D0 D5 20 30 DE A5 F5 A8 75 C7 EE 0B 0F 1B 88 C2 8A CC 6F 70 1D E4 D8 4E DD 04 A5 5B B8 04 B1 29 42 08 92 19 78 E2 26 EB 6B 07 49 DE 8A AF A3 41 72 1D E2 3C 62 0F 7E 7B DE A3 0F 71 8C 5D EC E9 96 96 45 A9 39 33 8A 87 C9 93 CE 3B 6D 75 50 21 1F 4C 03 E9 A7 AD 03 0F 5E A9 EE 60 CC EA 05 4F DF E1 B1 13 A6 7D C7 B9 37 58 53 3B 06 1A AD 98 E5 06 D9 74 2A B1 96 75 DE A6 B7 89 25 53 2A A3 07 B6 70 C6 86 1F 59 EB 53 08 57 6E 86 D7 A1 5C DB 26 D7 86 3E 97 BB FD 6A 0A 4C E1 81 B9 4C C1 A0 49 89 57 29 E0 CD 79 6F 0A 46 C1 C6 62 75 49 C6 9A B9 22 75 EE 10 C7 56 E6 D5 DE 4D EC 89 5A 6F AC 60 0F B3 CC 37 9E F2 BE 49 A7 77 3C 05 AE 92 66 C8 BE 16 E5 35 17 24 18 A5 CE B8 BB AE CD 88 DE 01 53 40 84 E0 06 C6 77 96 09 DF D7 76 3B CA C9 B5 B2 91 95 07 54 6F 51 EB 12 58 16 8A AF C3 E3 B9 4A EC 25 A5 D1 19 59 72 F5 E3 4F 7C 40 B2 D0 4E 9F 50 13 FB 86 C3 6A 88 32 5B 67 EC 4F 0E 0B 31 F8 0C 02 6C CE 8D 50 55 A2 B3 57 73 7C 78 D3 43 1F 48 33 51 E7 0A D0 6D 46 71 4A AD 66 50 F9 96 11 4F A5 5B 3C A0 3E 46 D2 CB 3B A1 03 84 9C 8E 4E 2D 83 69 2E 17 9B F8 36 63 F1 93 CA F9 32 57 2B AB 4E 14 A3 5A F1 39 B0 3F 0F 99 CC 9B FB 7E BC 0A AA C9 65 3C C8 B4 B0 1F
val flag2 = readByte().toInt() val flag2 = readByte().toInt()
PacketLogger.verbose { PacketLogger.verbose {
"包类型(flag2) = $flag2. (可能是 ${when (flag2) { "包类型(flag2) = $flag2. (可能是 ${when (flag2) {
...@@ -195,19 +191,10 @@ internal object KnownPacketFactories { ...@@ -195,19 +191,10 @@ internal object KnownPacketFactories {
readString(readInt() - 4)// uinAccount readString(readInt() - 4)// uinAccount
//debugPrint("remaining")
/* receive
* 00 00 00 0A
* 00
* 00
* 00 00 00 05 30 // uin
*/
ByteArrayPool.useInstance { data -> ByteArrayPool.useInstance { data ->
val size = this.readAvailable(data) val size = this.readAvailable(data)
kotlin.runCatching { kotlin.runCatching {
// 快速解密
when (flag2) { when (flag2) {
2 -> data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } } 2 -> data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } }
1 -> data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } } 1 -> data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } }
...@@ -215,20 +202,17 @@ internal object KnownPacketFactories { ...@@ -215,20 +202,17 @@ internal object KnownPacketFactories {
else -> error("") else -> error("")
} }
}.getOrElse { }.getOrElse {
// 慢速解密
PacketLogger.verbose { "失败, 尝试其他各种key" } PacketLogger.verbose { "失败, 尝试其他各种key" }
bot.client.tryDecryptOrNull(data, size) { it } bot.client.tryDecryptOrNull(data, size) { it }
}?.toReadPacket()?.let { decryptedData -> }?.toReadPacket()?.let { decryptedData ->
// 解析外层包装
when (flag1) { when (flag1) {
0x0A -> parseSsoFrame(bot, decryptedData) 0x0A -> parseSsoFrame(bot, decryptedData)
0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样. 0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样.
else -> error("unknown flag1: ${flag1.toByte().toUHexString()}") else -> error("unknown flag1: ${flag1.toByte().toUHexString()}")
} }
}?.let { }?.let {
// 处理内层真实的包
if (it.packetFactory == null) { if (it.packetFactory == null) {
bot.logger.debug("Received commandName: ${it.commandName}") bot.network.logger.debug("Received commandName: ${it.commandName}")
PacketLogger.warning { "找不到 PacketFactory" } PacketLogger.warning { "找不到 PacketFactory" }
PacketLogger.verbose { "传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> data.toUHexString(length = length) }}" } PacketLogger.verbose { "传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> data.toUHexString(length = length) }}" }
return return
...@@ -236,7 +220,7 @@ internal object KnownPacketFactories { ...@@ -236,7 +220,7 @@ internal object KnownPacketFactories {
it.data.withUse { it.data.withUse {
when (flag2) { when (flag2) {
0, 1 ->//it.data.parseUniResponse(bot, it.packetFactory, it.sequenceId, consumer) 0, 1 ->
when (it.packetFactory) { when (it.packetFactory) {
is OutgoingPacketFactory<*> -> consumer( is OutgoingPacketFactory<*> -> consumer(
it.packetFactory as OutgoingPacketFactory<T>, it.packetFactory as OutgoingPacketFactory<T>,
...@@ -252,13 +236,11 @@ internal object KnownPacketFactories { ...@@ -252,13 +236,11 @@ internal object KnownPacketFactories {
) )
} }
// for oicq response, factory is always OutgoingPacketFactory
2 -> it.data.parseOicqResponse(bot, it.packetFactory as OutgoingPacketFactory<T>, it.sequenceId, consumer) 2 -> it.data.parseOicqResponse(bot, it.packetFactory as OutgoingPacketFactory<T>, it.sequenceId, consumer)
else -> error("unknown flag2: $flag2. Body to be parsed for inner packet=${it.data.readBytes().toUHexString()}") else -> error("unknown flag2: $flag2. Body to be parsed for inner packet=${it.data.readBytes().toUHexString()}")
} }
} }
} ?: inline { } ?: inline {
// 无法解析
PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" } PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" }
return return
} }
...@@ -279,21 +261,14 @@ internal object KnownPacketFactories { ...@@ -279,21 +261,14 @@ internal object KnownPacketFactories {
*/ */
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket { private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket {
// * 00 00 00 2F 00 00 94 90 00 00 00 00 00 00 00 04 00 00 00 13 48 65 61 72 74 62 65 61 74 2E 41 6C 69 76 65
// 00 00 00 08
// 59 E7 DF 4F
// 00 00 00 00
//
// 00 00 00 04
val commandName: String val commandName: String
val ssoSequenceId: Int val ssoSequenceId: Int
val dataCompressed: Int val dataCompressed: Int
// head
input.readPacket(input.readInt() - 4).withUse { input.readPacket(input.readInt() - 4).withUse {
ssoSequenceId = readInt() ssoSequenceId = readInt()
PacketLogger.verbose { "sequenceId = $ssoSequenceId" } PacketLogger.verbose { "sequenceId = $ssoSequenceId" }
val returnCode = readInt() val returnCode = readInt()
check (returnCode == 0) { "returnCode = $returnCode" } check(returnCode == 0) { "returnCode = $returnCode" }
if (PacketLogger.isEnabled) { if (PacketLogger.isEnabled) {
val extraData = readBytes(readInt() - 4) val extraData = readBytes(readInt() - 4)
PacketLogger.verbose { "(sso/inner)extraData = ${extraData.toUHexString()}" } PacketLogger.verbose { "(sso/inner)extraData = ${extraData.toUHexString()}" }
...@@ -361,7 +336,7 @@ internal object KnownPacketFactories { ...@@ -361,7 +336,7 @@ internal object KnownPacketFactories {
this.discardExact(1) // const = 0 this.discardExact(1) // const = 0
val packet = when (encryptionMethod) { val packet = when (encryptionMethod) {
4 -> { // peer public key, ECDH 4 -> {
var data = this.decryptBy(bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt()) var data = this.decryptBy(bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt())
val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey()) val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey())
...@@ -379,7 +354,7 @@ internal object KnownPacketFactories { ...@@ -379,7 +354,7 @@ internal object KnownPacketFactories {
byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size) byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size)
}.getOrElse { }.getOrElse {
byteArrayBuffer.decryptBy(bot.client.randomKey, size) byteArrayBuffer.decryptBy(bot.client.randomKey, size)
}.toReadPacket() // 这里实际上应该用 privateKey(另一个random出来的key) }.toReadPacket()
} }
} else { } else {
this.decryptBy(bot.client.randomKey, 0, (this.remaining - 1).toInt()) this.decryptBy(bot.client.randomKey, 0, (this.remaining - 1).toInt())
......
...@@ -22,6 +22,7 @@ import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag ...@@ -22,6 +22,7 @@ import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag
import net.mamoe.mirai.qqandroid.utils.guidFlag import net.mamoe.mirai.qqandroid.utils.guidFlag
import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.cryptor.contentToString
import net.mamoe.mirai.utils.cryptor.decryptBy import net.mamoe.mirai.utils.cryptor.decryptBy
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
...@@ -359,27 +360,30 @@ internal class WtLogin{ ...@@ -359,27 +360,30 @@ internal class WtLogin{
@UseExperimental(MiraiDebugAPI::class) @UseExperimental(MiraiDebugAPI::class)
private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha { private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha {
// val ret = tlvMap[0x104]?.let { println(it.toUHexString()) } // val ret = tlvMap[0x104]?.let { println(it.toUHexString()) }
bot.client.t104 = tlvMap.getOrFail(0x104)
tlvMap[0x192]?.let { tlvMap[0x192]?.let {
bot.client.t104 = tlvMap.getOrFail(0x104)
return LoginPacketResponse.Captcha.Slider(it.encodeToString()) return LoginPacketResponse.Captcha.Slider(it.encodeToString())
} }
tlvMap[0x165]?.let { question -> tlvMap[0x165]?.let { question ->
if (question[18].toInt() == 0x36) { if (question[18].toInt() == 0x36) {
//图片验证 //图片验证
DebugLogger.debug("是一个图片验证码") DebugLogger.debug("是一个图片验证码")
bot.client.t104 = tlvMap.getOrFail(0x104)
val imageData = tlvMap.getOrFail(0x105).toReadPacket() val imageData = tlvMap.getOrFail(0x105).toReadPacket()
val signInfoLength = imageData.readShort() val signInfoLength = imageData.readShort()
imageData.discardExact(2)//image Length imageData.discardExact(2)//image Length
val sign = imageData.readBytes(signInfoLength.toInt()) val sign = imageData.readBytes(signInfoLength.toInt())
val buffer = IoBuffer.Pool.borrow()
imageData.readFully(buffer)
return LoginPacketResponse.Captcha.Picture( return LoginPacketResponse.Captcha.Picture(
data = imageData.readBytes().toIoBuffer(), data = buffer,
sign = sign sign = sign
) )
} else error("UNKNOWN CAPTCHA QUESTION: $question") } else error("UNKNOWN CAPTCHA QUESTION: ${question.toUHexString()}, tlvMap=" + tlvMap.contentToString())
} }
error("UNKNOWN CAPTCHA") error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap.contentToString())
} }
@UseExperimental(MiraiDebugAPI::class) @UseExperimental(MiraiDebugAPI::class)
......
...@@ -61,13 +61,13 @@ kotlin { ...@@ -61,13 +61,13 @@ kotlin {
languageSettings.useExperimentalAnnotation("kotlin.Experimental") languageSettings.useExperimentalAnnotation("kotlin.Experimental")
dependencies { dependencies {
implementation(kotlin("stdlib", kotlinVersion)) api(kotlin("stdlib", kotlinVersion))
implementation(kotlin("serialization", kotlinVersion)) api(kotlin("serialization", kotlinVersion))
implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
implementation(kotlinx("io", kotlinXIoVersion)) api(kotlinx("io", kotlinXIoVersion))
implementation(kotlinx("coroutines-io", coroutinesIoVersion)) api(kotlinx("coroutines-io", coroutinesIoVersion))
implementation(kotlinx("coroutines-core", coroutinesVersion)) api(kotlinx("coroutines-core", coroutinesVersion))
} }
} }
commonMain { commonMain {
......
...@@ -184,4 +184,4 @@ interface Group : Contact, CoroutineScope { ...@@ -184,4 +184,4 @@ interface Group : Contact, CoroutineScope {
/** /**
* 返回机器人是否正在被禁言 * 返回机器人是否正在被禁言
*/ */
val Group.isBotMuted: Boolean get() = this.botMuteRemaining == 0 val Group.isBotMuted: Boolean get() = this.botMuteRemaining != 0
...@@ -29,20 +29,20 @@ import kotlin.contracts.contract ...@@ -29,20 +29,20 @@ import kotlin.contracts.contract
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl @MessageDsl
inline fun CoroutineScope.subscribeMessages(crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> Unit) { inline fun <R> CoroutineScope.subscribeMessages(crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R): R {
// contract 可帮助 IDE 进行类型推断. 无实际代码作用. // contract 可帮助 IDE 进行类型推断. 无实际代码作用.
contract { contract {
callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) callsInPlace(listeners, InvocationKind.EXACTLY_ONCE)
} }
MessageSubscribersBuilder { messageListener: MessageListener<MessagePacket<*, *>> -> return MessageSubscribersBuilder { messageListener: MessageListener<MessagePacket<*, *>> ->
// subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [listener] // subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [listener]
// listener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块. // listener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块.
subscribeAlways { subscribeAlways {
messageListener.invoke(this, this.message.toString()) messageListener.invoke(this, this.message.toString())
// this.message.toString() 即为 messageListener 中 it 接收到的值 // this.message.toString() 即为 messageListener 中 it 接收到的值
} }
}.apply { listeners() } }.run(listeners)
} }
/** /**
...@@ -50,15 +50,15 @@ inline fun CoroutineScope.subscribeMessages(crossinline listeners: MessageSubscr ...@@ -50,15 +50,15 @@ inline fun CoroutineScope.subscribeMessages(crossinline listeners: MessageSubscr
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl @MessageDsl
inline fun CoroutineScope.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> Unit) { inline fun <R> CoroutineScope.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R): R {
contract { contract {
callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) callsInPlace(listeners, InvocationKind.EXACTLY_ONCE)
} }
MessageSubscribersBuilder<GroupMessage> { listener -> return MessageSubscribersBuilder<GroupMessage> { listener ->
subscribeAlways { subscribeAlways {
listener(it, this.message.toString()) listener(this, this.message.toString())
} }
}.apply { listeners() } }.run(listeners)
} }
/** /**
...@@ -66,15 +66,15 @@ inline fun CoroutineScope.subscribeGroupMessages(crossinline listeners: MessageS ...@@ -66,15 +66,15 @@ inline fun CoroutineScope.subscribeGroupMessages(crossinline listeners: MessageS
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl @MessageDsl
inline fun CoroutineScope.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> Unit) { inline fun <R> CoroutineScope.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R): R {
contract { contract {
callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) callsInPlace(listeners, InvocationKind.EXACTLY_ONCE)
} }
MessageSubscribersBuilder<FriendMessage> { listener -> return MessageSubscribersBuilder<FriendMessage> { listener ->
subscribeAlways { subscribeAlways {
listener(it, this.message.toString()) listener(this, this.message.toString())
} }
}.apply { listeners() } }.run(listeners)
} }
/** /**
...@@ -82,15 +82,15 @@ inline fun CoroutineScope.subscribeFriendMessages(crossinline listeners: Message ...@@ -82,15 +82,15 @@ inline fun CoroutineScope.subscribeFriendMessages(crossinline listeners: Message
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl @MessageDsl
inline fun Bot.subscribeMessages(crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> Unit) { inline fun <R> Bot.subscribeMessages(crossinline listeners: MessageSubscribersBuilder<MessagePacket<*, *>>.() -> R): R {
contract { contract {
callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) callsInPlace(listeners, InvocationKind.EXACTLY_ONCE)
} }
MessageSubscribersBuilder<MessagePacket<*, *>> { listener -> return MessageSubscribersBuilder<MessagePacket<*, *>> { listener ->
this.subscribeAlways { this.subscribeAlways {
listener(it, this.message.toString()) listener(this, this.message.toString())
} }
}.apply { listeners() } }.run(listeners)
} }
/** /**
...@@ -98,15 +98,15 @@ inline fun Bot.subscribeMessages(crossinline listeners: MessageSubscribersBuilde ...@@ -98,15 +98,15 @@ inline fun Bot.subscribeMessages(crossinline listeners: MessageSubscribersBuilde
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl @MessageDsl
inline fun Bot.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> Unit) { inline fun <R> Bot.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder<GroupMessage>.() -> R): R {
contract { contract {
callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) callsInPlace(listeners, InvocationKind.EXACTLY_ONCE)
} }
MessageSubscribersBuilder<GroupMessage> { listener -> return MessageSubscribersBuilder<GroupMessage> { listener ->
this.subscribeAlways { this.subscribeAlways {
listener(it, this.message.toString()) listener(this, this.message.toString())
} }
}.apply { listeners() } }.run(listeners)
} }
/** /**
...@@ -114,15 +114,15 @@ inline fun Bot.subscribeGroupMessages(crossinline listeners: MessageSubscribersB ...@@ -114,15 +114,15 @@ inline fun Bot.subscribeGroupMessages(crossinline listeners: MessageSubscribersB
*/ */
@UseExperimental(ExperimentalContracts::class) @UseExperimental(ExperimentalContracts::class)
@MessageDsl @MessageDsl
inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> Unit) { inline fun <R> Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder<FriendMessage>.() -> R): R {
contract { contract {
callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) callsInPlace(listeners, InvocationKind.EXACTLY_ONCE)
} }
MessageSubscribersBuilder<FriendMessage> { listener -> return MessageSubscribersBuilder<FriendMessage> { listener ->
this.subscribeAlways { this.subscribeAlways {
it.listener(it.message.toString()) listener(this, this.message.toString())
} }
}.apply { listeners() } }.run(listeners)
} }
...@@ -588,6 +588,12 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>( ...@@ -588,6 +588,12 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
return content({ it.trim() == toCheck }, { reply(reply) }) return content({ it.trim() == toCheck }, { reply(reply) })
} }
@MessageDsl
infix fun String.reply(reply: Message): Listener<T> {
val toCheck = this.trim()
return content({ it.trim() == toCheck }, { reply(reply) })
}
@MessageDsl @MessageDsl
inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener<T> { inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener<T> {
val toCheck = this.trim() val toCheck = this.trim()
......
...@@ -10,11 +10,13 @@ ...@@ -10,11 +10,13 @@
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.CompletableJob
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.internal.Handler import net.mamoe.mirai.event.internal.Handler
import net.mamoe.mirai.event.internal.subscribeInternal import net.mamoe.mirai.event.internal.subscribeInternal
import kotlin.coroutines.CoroutineContext
/* /*
* 该文件为所有的订阅事件的方法. * 该文件为所有的订阅事件的方法.
...@@ -38,6 +40,8 @@ enum class ListeningStatus { ...@@ -38,6 +40,8 @@ enum class ListeningStatus {
/** /**
* 事件监听器. * 事件监听器.
* 由 [subscribe] 等方法返回. * 由 [subscribe] 等方法返回.
*
* 取消监听: [complete]
*/ */
interface Listener<in E : Event> : CompletableJob { interface Listener<in E : Event> : CompletableJob {
suspend fun onEvent(event: E): ListeningStatus suspend fun onEvent(event: E): ListeningStatus
...@@ -50,11 +54,10 @@ interface Listener<in E : Event> : CompletableJob { ...@@ -50,11 +54,10 @@ interface Listener<in E : Event> : CompletableJob {
* 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行. * 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行.
* *
* 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听. * 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听.
* 或 [Listener] complete 时结束. * 或 [Listener.complete] 后结束.
*
* *
* **注意**: 这个函数返回 [Listener], 它是一个 [CompletableJob]. 如果不手动 [CompletableJob.complete], 它将会阻止当前 [CoroutineScope] 结束. * 这个函数返回 [Listener], 它是一个 [CompletableJob]. 请注意它除非被 [Listener.complete] 或 [Listener.cancel], 则不会完成.
* 例: * 例:
* ```kotlin * ```kotlin
* runBlocking { // this: CoroutineScope * runBlocking { // this: CoroutineScope
* subscribe<Event> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob * subscribe<Event> { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob
...@@ -70,23 +73,37 @@ interface Listener<in E : Event> : CompletableJob { ...@@ -70,23 +73,37 @@ interface Listener<in E : Event> : CompletableJob {
* ``` * ```
* *
* *
* 要创建一个仅在机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]): * 要创建一个仅在某个机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]):
* ```kotlin * ```kotlin
* bot.subscribe<Subscribe> { /* 一些处理 */ } * bot.subscribe<Subscribe> { /* 一些处理 */ }
* ``` * ```
* *
*
* 事件处理时的 [CoroutineContext] 为调用本函数时的 [receiver][this] [CoroutineScope.coroutineContext].
* 因此:
* - 事件处理时抛出的异常将会在 [this] [CoroutineExceptionHandler] 中处理
* [this] 没有 [CoroutineExceptionHandler], 则在事件广播方的 [CoroutineExceptionHandler] 处理
* 若均找不到, 则会触发 logger warning.
* - 事件处理时抛出异常不会停止监听器.
* - 建议在事件处理中, [handler] 里处理异常, 或在 [this] 指定 [CoroutineExceptionHandler].
*
*
* **注意:** 事件处理是 `suspend` , 请严格控制 JVM 阻塞方法的使用. 若致事件处理阻塞, 则会导致一些逻辑无法进行.
*
* // TODO: 2020/2/13 在 bot 下监听时同时筛选对应 bot 实例
*
* @see subscribeMessages 监听消息 DSL * @see subscribeMessages 监听消息 DSL
* @see subscribeGroupMessages 监听群消息 * @see subscribeGroupMessages 监听群消息 DSL
* @see subscribeFriendMessages 监听好友消息 * @see subscribeFriendMessages 监听好友消息 DSL
*/ */
inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> = inline fun <reified E : Event> CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener<E> =
E::class.subscribeInternal(Handler { it.handler(it) }) E::class.subscribeInternal(Handler { it.handler(it); })
/** /**
* 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件.
* 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行. * 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行.
* *
* 仅当 [Listener] complete 时结束. * 仅当 [Listener.complete] 或 [Listener.cancel] 时结束.
* *
* @see subscribe 获取更多说明 * @see subscribe 获取更多说明
*/ */
......
...@@ -29,6 +29,8 @@ class EventCancelledException : RuntimeException { ...@@ -29,6 +29,8 @@ class EventCancelledException : RuntimeException {
constructor(cause: Throwable?) : super(cause) constructor(cause: Throwable?) : super(cause)
} }
// note: 若你使用 IntelliJ IDEA, 按 alt + 7 可打开结构
// region Bot 状态 // region Bot 状态
......
...@@ -53,7 +53,7 @@ interface GroupMemberEvent : GroupEvent { ...@@ -53,7 +53,7 @@ interface GroupMemberEvent : GroupEvent {
/** /**
* 有关的事件 * 有关好友的事件
*/ */
interface FriendEvent : BotEvent { interface FriendEvent : BotEvent {
val friend: QQ val friend: QQ
......
...@@ -14,9 +14,11 @@ import net.mamoe.mirai.contact.Group ...@@ -14,9 +14,11 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.message.data.At import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.getValue
import net.mamoe.mirai.utils.unsafeWeakRef import net.mamoe.mirai.utils.unsafeWeakRef
import kotlin.jvm.JvmName
@Suppress("unused", "NOTHING_TO_INLINE") @Suppress("unused", "NOTHING_TO_INLINE")
class GroupMessage( class GroupMessage(
...@@ -37,6 +39,28 @@ class GroupMessage( ...@@ -37,6 +39,28 @@ class GroupMessage(
inline fun At.member(): Member = group[this.target] inline fun At.member(): Member = group[this.target]
inline fun Long.member(): Member = group[this] inline fun Long.member(): Member = group[this]
/**
* 给这个消息事件的主体发送引用回复消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
suspend inline fun quoteReply(message: MessageChain) = reply(this.message.quote() + message)
suspend inline fun quoteReply(message: Message) = reply(this.message.quote() + message)
suspend inline fun quoteReply(plain: String) = reply(this.message.quote() + plain)
@JvmName("reply2")
suspend inline fun String.quoteReply() = quoteReply(this)
@JvmName("reply2")
suspend inline fun Message.quoteReply() = quoteReply(this)
@JvmName("reply2")
suspend inline fun MessageChain.quoteReply() = quoteReply(this)
override fun toString(): String = override fun toString(): String =
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
} }
\ No newline at end of file
...@@ -17,6 +17,8 @@ import net.mamoe.mirai.utils.MiraiInternalAPI ...@@ -17,6 +17,8 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
* At 一个人. 只能发送给一个群. * At 一个人. 只能发送给一个群.
*
* @see AtAll 全体成员
*/ */
class At @MiraiInternalAPI constructor(val target: Long, val display: String) : Message { class At @MiraiInternalAPI constructor(val target: Long, val display: String) : Message {
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
...@@ -33,10 +35,10 @@ class At @MiraiInternalAPI constructor(val target: Long, val display: String) : ...@@ -33,10 +35,10 @@ class At @MiraiInternalAPI constructor(val target: Long, val display: String) :
// 自动为消息补充 " " // 自动为消息补充 " "
override fun followedBy(tail: Message): MessageChain { override fun followedBy(tail: Message): MessageChain {
if(tail is PlainText && !tail.stringValue.startsWith(' ')){ if(tail is PlainText && tail.stringValue.startsWith(' ')){
return super.followedBy(PlainText(" ")) + tail return super.followedBy(tail)
} }
return super.followedBy(tail) return super.followedBy(PlainText(" ")) + tail
} }
} }
......
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.message.data
/**
* "@全体成员"
*
* @see At at 单个群成员
*/
object AtAll : Message {
override fun toString(): String = "@全体成员"
// 自动为消息补充 " "
override fun followedBy(tail: Message): MessageChain {
if(tail is PlainText && tail.stringValue.startsWith(' ')){
return super.followedBy(tail)
}
return super.followedBy(PlainText(" ")) + tail
}
}
\ No newline at end of file
...@@ -54,16 +54,6 @@ class BotConfiguration { ...@@ -54,16 +54,6 @@ class BotConfiguration {
*/ */
var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext
/**
* 连接每个服务器的时间
*/
var touchTimeoutMillis: Long = 1.secondsToMillis
/**
* 是否使用随机的设备名.
* 使用随机可以降低被封禁的风险, 但可能导致每次登录都需要输入验证码
* 当一台设备只登录少量账号时, 将此项设置为 `false` 可能更好.
*/
var randomDeviceName: Boolean = false
/** /**
* 心跳周期. 过长会导致被服务器断开连接. * 心跳周期. 过长会导致被服务器断开连接.
*/ */
...@@ -85,20 +75,10 @@ class BotConfiguration { ...@@ -85,20 +75,10 @@ class BotConfiguration {
* 最多尝试多少次重连 * 最多尝试多少次重连
*/ */
var reconnectionRetryTimes: Int = 3 var reconnectionRetryTimes: Int = 3
/**
* 有验证码要求就失败
*/
var failOnCaptcha = false
/** /**
* 验证码处理器 * 验证码处理器
*/ */
var loginSolver: LoginSolver = defaultLoginSolver var loginSolver: LoginSolver = defaultLoginSolver
/**
* 登录完成后几秒会收到好友消息的历史记录,
* 这些历史记录不会触发事件.
* 这个选项为是否把这些记录添加到日志
*/
var logPreviousMessages: Boolean = false
companion object { companion object {
/** /**
......
...@@ -15,10 +15,7 @@ package net.mamoe.mirai.utils.io ...@@ -15,10 +15,7 @@ package net.mamoe.mirai.utils.io
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.io.pool.useInstance import kotlinx.io.pool.useInstance
import net.mamoe.mirai.utils.DefaultLogger import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.withSwitch
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
...@@ -28,7 +25,7 @@ import kotlin.jvm.JvmName ...@@ -28,7 +25,7 @@ import kotlin.jvm.JvmName
@MiraiDebugAPI("Unsatble") @MiraiDebugAPI("Unsatble")
object DebugLogger : MiraiLogger by DefaultLogger("Packet Debug").withSwitch() val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(false)
@MiraiDebugAPI("Unstable") @MiraiDebugAPI("Unstable")
inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this) inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this)
......
...@@ -74,3 +74,9 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { ...@@ -74,3 +74,9 @@ actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray {
return output.toByteArray() return output.toByteArray()
} }
} }
/**
* 时间戳
*/
actual val currentTimeMillis: Long
get() = System.currentTimeMillis()
\ No newline at end of file
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
package demo.subscribe package demo.subscribe
import kotlinx.coroutines.CompletableJob
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.alsoLogin
...@@ -19,6 +20,7 @@ import net.mamoe.mirai.contact.sendMessage ...@@ -19,6 +20,7 @@ import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.*
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.data.AtAll
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.data.firstOrNull import net.mamoe.mirai.message.data.firstOrNull
...@@ -44,12 +46,13 @@ private fun readTestAccount(): BotAccount? { ...@@ -44,12 +46,13 @@ private fun readTestAccount(): BotAccount? {
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
suspend fun main() { suspend fun main() {
val bot = QQAndroid.Bot( // JVM 下也可以不写 `TIMPC.` 引用顶层函数 val bot = QQAndroid.Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
1994701121, 123456789,
"123456" "123456"
) { ) {
// 覆盖默认的配置 // 覆盖默认的配置
randomDeviceName = false
// networkLoggerSupplier = { SilentLogger } // 禁用网络层输出
}.alsoLogin() }.alsoLogin()
bot.messageDSL() bot.messageDSL()
...@@ -96,7 +99,7 @@ fun Bot.messageDSL() { ...@@ -96,7 +99,7 @@ fun Bot.messageDSL() {
// it: String (MessageChain.toString) // it: String (MessageChain.toString)
message[Image].download() // message[Image].download() // 还未支持 download
if (this is GroupMessage) { if (this is GroupMessage) {
//如果是群消息 //如果是群消息
// group: Group // group: Group
...@@ -118,6 +121,7 @@ fun Bot.messageDSL() { ...@@ -118,6 +121,7 @@ fun Bot.messageDSL() {
// 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String // 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String
"我的qq" reply { sender.id } "我的qq" reply { sender.id }
"at all" reply AtAll // at 全体成员
// 如果是这个 QQ 号发送的消息(可以是好友消息也可以是群消息) // 如果是这个 QQ 号发送的消息(可以是好友消息也可以是群消息)
sentBy(123456789) { sentBy(123456789) {
...@@ -133,12 +137,27 @@ fun Bot.messageDSL() { ...@@ -133,12 +137,27 @@ fun Bot.messageDSL() {
} }
// 当消息中包含 "复读" 时 // listener 管理
val listener = (contains("复读1") or contains("复读2")) {
reply(message) var repeaterListener: CompletableJob? = null
contains("开启复读") {
repeaterListener?.complete()
bot.subscribeGroupMessages {
repeaterListener = contains("复读") {
reply(message)
}
}
}
contains("关闭复读") {
if (repeaterListener?.complete() == null) {
reply("没有开启复读")
} else {
reply("成功关闭复读")
}
} }
listener.complete() // 停止监听
// 自定义的 filter, filter 中 it 为转为 String 的消息. // 自定义的 filter, filter 中 it 为转为 String 的消息.
// 也可以用任何能在处理时使用的变量, 如 subject, sender, message // 也可以用任何能在处理时使用的变量, 如 subject, sender, message
...@@ -196,19 +215,19 @@ suspend fun directlySubscribe(bot: Bot) { ...@@ -196,19 +215,19 @@ suspend fun directlySubscribe(bot: Bot) {
// 在当前协程作用域 (CoroutineScope) 下创建一个子 Job, 监听一个事件. // 在当前协程作用域 (CoroutineScope) 下创建一个子 Job, 监听一个事件.
// //
// 手动处理消息 // 手动处理消息
// 使用 Bot 的扩展方法监听, 将在处理事件时得到一个 this: Bot.
// 这样可以调用 Bot 内的一些扩展方法如 UInt.qq():QQ
// //
// 这个函数返回 Listener, Listener 是一个 CompletableJob. 如果不手动 close 它, 它会一直阻止当前 CoroutineScope 结束. // subscribeAlways 函数返回 Listener, Listener 是一个 CompletableJob.
//
// 例如: // 例如:
// ```kotlin // ```kotlin
// runBlocking {// this: CoroutineScope // runBlocking {// this: CoroutineScope
// bot.subscribeAlways<FriendMessage> { // subscribeAlways<FriendMessage> {
// } // }
// } // }
// ``` // ```
// 则这个 `runBlocking` 永远不会结束, 因为 `subscribeAlways` 在 `runBlocking` 的 `CoroutineScope` 下创建了一个 Job. // 则这个 `runBlocking` 永远不会结束, 因为 `subscribeAlways` 在 `runBlocking` 的 `CoroutineScope` 下创建了一个 Job.
// 正确的用法为: // 正确的用法为:
// 在 Bot 的 CoroutineScope 下创建一个监听事件的 Job, 则这个子 Job 会在 Bot 离线后自动完成 (complete).
bot.subscribeAlways<FriendMessage> { bot.subscribeAlways<FriendMessage> {
// this: FriendMessageEvent // this: FriendMessageEvent
// event: FriendMessageEvent // event: FriendMessageEvent
......
...@@ -47,7 +47,7 @@ class GentleImage(val contact: Contact, val keyword: String) { ...@@ -47,7 +47,7 @@ class GentleImage(val contact: Contact, val keyword: String) {
Jsoup.connect("https://api.lolicon.app/setu/?r18=$r18" + if (keyword.isNotBlank()) "&keyword=$keyword&num=10" else "") Jsoup.connect("https://api.lolicon.app/setu/?r18=$r18" + if (keyword.isNotBlank()) "&keyword=$keyword&num=10" else "")
.ignoreContentType(true) .ignoreContentType(true)
.userAgent(UserAgent.randomUserAgent) .userAgent(UserAgent.randomUserAgent)
.proxy("127.0.0.1", 1088) // .proxy("127.0.0.1", 1088)
.timeout(10_0000) .timeout(10_0000)
.get().body().text() .get().body().text()
) )
...@@ -60,7 +60,7 @@ class GentleImage(val contact: Contact, val keyword: String) { ...@@ -60,7 +60,7 @@ class GentleImage(val contact: Contact, val keyword: String) {
.ignoreContentType(true) .ignoreContentType(true)
.userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27") .userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27")
.referrer("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=${setu.pid}") .referrer("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=${setu.pid}")
.proxy("127.0.0.1", 1088) // .proxy("127.0.0.1", 1088)
.ignoreHttpErrors(true) .ignoreHttpErrors(true)
.maxBodySize(10000000) .maxBodySize(10000000)
.execute().also { check(it.statusCode() == 200) { "Failed to download image" } } .execute().also { check(it.statusCode() == 200) { "Failed to download image" } }
......
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