Merge remote-tracking branch 'mirai/dev' into dev

This commit is contained in:
Him188 2021-02-24 09:17:24 +08:00
commit 333103eca7
15 changed files with 286 additions and 60 deletions

View File

@ -4706,12 +4706,13 @@ public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum {
public static fun values ()[Lnet/mamoe/mirai/message/data/MusicKind;
}
public final class net/mamoe/mirai/message/data/MusicShare : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
public final class net/mamoe/mirai/message/data/MusicShare : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
public static final field Key Lnet/mamoe/mirai/message/data/MusicShare$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun <init> (ILnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public fun <init> (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun <init> (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
public fun appendMiraiCodeTo (Ljava/lang/StringBuilder;)V
public final fun component1 ()Lnet/mamoe/mirai/message/data/MusicKind;
public final fun component2 ()Ljava/lang/String;
public final fun component3 ()Ljava/lang/String;

View File

@ -104,7 +104,6 @@ allprojects {
maven(url = "https://kotlin.bintray.com/kotlinx")
google()
mavenCentral()
maven(url = "https://dl.bintray.com/karlatemp/misc")
}
afterEvaluate {

View File

@ -12,7 +12,7 @@
import org.gradle.api.attributes.Attribute
object Versions {
const val project = "2.5.0-dev-1"
const val project = "2.5.0-dev-2"
const val core = project
const val console = project
@ -103,4 +103,4 @@ const val yamlkt = "net.mamoe.yamlkt:yamlkt:${Versions.yamlkt}"
const val `jetbrains-annotations` = "org.jetbrains:annotations:19.0.0"
const val `caller-finder` = "io.github.karlatemp:caller:1.0.1"
const val `caller-finder` = "io.github.karlatemp:caller:1.1.1"

View File

@ -68,6 +68,25 @@ workingDir = File("C:/mirai")
setWorkingDir(File("C:/mirai"))
```
#### 修改缓存目录
缓存目录会相对于 `workingDir` 解析。如 `File("cache")` 将会解析为 `workingDir` 内的 `cache` 目录。而 `File("C:/cache")` 将会解析为绝对的 `C:/cache` 目录。
默认为 `File("cache")`
要修改缓存目录(自 mirai 2.4.0
```
// Kotlin
cacheDir = File("cache") // 最终为 workingDir 目录中的 cache 目录
cacheDir = File("C:/cache") // 最终为 C:/cache
// Java
setCacheDir(File("cache")) // 最终为 workingDir 目录中的 cache 目录
setCacheDir(File("C:/cache")) // 最终为 C:/cache
```
目前缓存目录会存储列表缓存、登录服务器、资源会话秘钥等。这些数据的存储方式有可能变化,请不要修改缓存目录中的文件。
#### 设备信息
Bot 默认使用全随机的设备信息。**在更换账号地点时候使用随机设备信息可能会导致无法登录**,当然,**成功登录时使用的设备信息也可以保存后在新的设备使用**。
@ -102,7 +121,6 @@ protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD
// Java
setProtocol(MiraiProtocol.ANDROID_PAD)
```
#### 重定向日志
Bot 有两个日志类别,`Bot` 或 `Net`。`Bot` 为通常日志,如收到事件。`Net` 为网络日志,包含收到和发出的每一个包和网络层解析时遇到的错误。
@ -149,6 +167,34 @@ setLoginSolver(new YourLoginSolver())
> 要获取更多有关 `LoginSolver` 的信息,查看 [LoginSolver.kt](../mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt#L32)
#### 启用列表缓存
Mirai 在启动时会拉取全部好友列表和群成员列表。当账号拥有过多群时登录可能缓慢,开启列表缓存会大幅加速登录过程。
Mirai 自动根据事件更新列表,并在每次登录时与服务器校验缓存有效性,**但有时候可能发生意外情况导致列表没有同步。如果出现找不到群员或好友等不同步情况,请关闭缓存并[提交 Bug](https://github.com/mamoe/mirai/issues/new?assignees=&labels=question&template=bug.md)**
要开启列表缓存(自 mirai 2.4.0
```
// 开启所有列表缓存
enableContactCache()
```
也可以只开启部分缓存:
```
// Kotlin
contactListCache {
friendListCacheEnabled = true // 开启好友列表缓存
groupMemberListCacheEnabled = true // 开启群成员列表缓存
saveIntervalMillis = 60_000 // 可选设置有更新时的保存时间间隔, 默认 60 秒
}
// Java
contactListCache.setFriendListCacheEnabled(true) // 开启好友列表缓存
contactListCache.setGroupMemberListCacheEnabled(true) // 开启群成员列表缓存
contactListCache.setSaveIntervalMillis(60000) // 可选设置有更新时的保存时间间隔, 默认 60 秒
```
### 获取当前所有 `Bot` 实例
在登录后 `Bot` 实例会被自动记录。可在 `Bot.instances` 获取到当前**在线**的所有 `Bot` 列表。
@ -168,11 +214,14 @@ setLoginSolver(new YourLoginSolver())
### 常见登录失败原因
[#993]: https://github.com/mamoe/mirai/discussions/993
| 错误信息 | 可能的原因 | 可能的解决方案 |
|:--------------|:---------------|:----------------------|
| 当前版本过低 | 密码错误 | 检查密码 |
| 当前上网环境异常 | 设备锁 | 开启或关闭设备锁后重试登录 |
| 当前版本过低 | 密码错误 | 检查密码或修改密码到 16 位以内 |
| 当前上网环境异常 | 设备锁 | 开启或关闭设备锁 (登录保护) |
| 禁止登录 | 需要处理滑块验证码 | [project-mirai/mirai-login-solver-selenium] |
| 密码错误 | 密码错误或过长 | 手机协议最大支持 16 位密码 ([#993]). 在官方 PC 客户端登录后修改密码 |
若以上方案无法解决问题,请尝试 [切换登录协议](#切换登录协议) 和 **[处理滑动验证码](#处理滑动验证码)**。

View File

@ -475,6 +475,7 @@ public open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, R
internal suspend inline fun executeAndReply(m: M, replier: suspend M.(String) -> Any?): RR {
when (val message = replier(m, m.message.contentToString())) {
is Message -> m.subject.sendMessage(message)
null,
is Unit -> Unit
else -> m.subject.sendMessage(message.toString())
}
@ -485,6 +486,7 @@ public open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, R
internal suspend inline fun executeAndQuoteReply(m: M, replier: suspend M.(String) -> Any?): RR {
when (val message = replier(m, m.message.contentToString())) {
is Message -> m.subject.sendMessage(m.message.quote() + message)
null,
is Unit -> Unit
else -> m.subject.sendMessage(m.message.quote() + message.toString())
}

View File

@ -25,13 +25,7 @@ internal fun String.parseMiraiCodeImpl(contact: Contact?): MessageChain = buildM
add(PlainText(origin.decodeMiraiCode()))
return@forEachMiraiCode
}
parser.argsRegex.matchEntire(args)
?.destructured
?.let {
parser.runCatching {
contact.mapper(it)
}.getOrNull()
}
parser.parse(contact, args)
?.let(::add)
?: add(PlainText(origin.decodeMiraiCode()))
}
@ -128,12 +122,76 @@ private object MiraiCodeParsers : Map<String, MiraiCodeParser> by mapOf(
"dice" to MiraiCodeParser(Regex("""([1-6])""")) { (value) ->
Dice(value.toInt())
},
"musicshare" to MiraiCodeParser.DynamicParser(7) { args ->
val (kind, title, summary, jumpUrl, pictureUrl) = args
val musicUrl = args[5]
val brief = args[6]
MusicShare(MusicKind.valueOf(kind), title, summary, jumpUrl, pictureUrl, musicUrl, brief)
},
)
private class MiraiCodeParser(
val argsRegex: Regex,
val mapper: Contact?.(MatchResult.Destructured) -> Message?
)
// Visitable for test
internal sealed class MiraiCodeParser {
abstract fun parse(contact: Contact?, args: String): Message?
class RegexParser(
private val argsRegex: Regex,
private val mapper: Contact?.(MatchResult.Destructured) -> Message?
) : MiraiCodeParser() {
override fun parse(contact: Contact?, args: String): Message? =
argsRegex.matchEntire(args)
?.destructured
?.let {
runCatching {
contact.mapper(it)
}.getOrNull()
}
}
class DynamicParser(
private val minArgs: Int,
private val maxArgs: Int = minArgs,
private val parser: (Contact?.(args: Array<String>) -> Message?),
) : MiraiCodeParser() {
override fun parse(contact: Contact?, args: String): Message? {
val ranges = mutableListOf<IntRange>()
if (args.isNotEmpty()) {
var begin = 0
var pos = 0
val len = args.length
while (pos < len) {
when (args[pos]) {
'\\' -> pos += 2
',' -> {
ranges.add(begin..pos)
pos++
begin = pos
}
else -> pos++
}
}
ranges.add(begin..len)
}
if (ranges.size < minArgs) return null
if (ranges.size > maxArgs) return null
@Suppress("RemoveExplicitTypeArguments")
val args0 = Array<String>(ranges.size) { index ->
val range = ranges[index]
args.substring(range.first, range.last).decodeMiraiCode()
}
runCatching {
return parser(contact, args0)
}
return null
}
}
}
private fun MiraiCodeParser(
argsRegex: Regex,
mapper: Contact?.(MatchResult.Destructured) -> Message?
): MiraiCodeParser = MiraiCodeParser.RegexParser(argsRegex, mapper)
internal fun StringBuilder.appendStringAsMiraiCode(value: String): StringBuilder = apply {
value.forEach { char ->

View File

@ -13,6 +13,8 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.safeCast
@ -53,7 +55,7 @@ public data class MusicShare(
* 在消息列表显示
*/
public val brief: String,
) : MessageContent, ConstrainSingle {
) : MessageContent, ConstrainSingle, CodableMessage {
public constructor(
/**
@ -88,6 +90,19 @@ public data class MusicShare(
override fun contentToString(): String =
brief.takeIf { it.isNotBlank() } ?: "[分享]$title" // empty content is not accepted by `sendMessage`
override fun appendMiraiCodeTo(builder: StringBuilder) {
builder.append("[mirai:musicshare:")
.append(kind.name)
.append(',').appendStringAsMiraiCode(title)
.append(',').appendStringAsMiraiCode(summary)
.append(',').appendStringAsMiraiCode(jumpUrl)
.append(',').appendStringAsMiraiCode(pictureUrl)
.append(',').appendStringAsMiraiCode(musicUrl)
.append(',').appendStringAsMiraiCode(brief)
.append(']')
}
// MusicShare(type=NeteaseCloudMusic, title='ファッション', summary='rinahamu/Yunomi', brief='', url='http://music.163.com/song/1338728297/?userid=324076307', pictureUrl='http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg', musicUrl='http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307')
/**

View File

@ -277,8 +277,8 @@ public open class BotConfiguration { // open for Java
*
* - 默认打印到标准输出, 通过 [MiraiLogger.create]
* - 忽略所有日志: [noBotLog]
* - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }`
* - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }`
* - 重定向到一个目录: `botLoggerSupplier = { DirectoryLogger("Bot ${it.id}") }`
* - 重定向到一个文件: `botLoggerSupplier = { SingleFileLogger("Bot ${it.id}") }`
*
* @see MiraiLogger
*/

View File

@ -10,11 +10,27 @@
package net.mamoe.mirai.message.code
import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode
import net.mamoe.mirai.message.code.internal.MiraiCodeParser
import net.mamoe.mirai.message.data.*
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class TestMiraiCode {
@Test
fun testDynamicMiraiCodeParser() {
fun runTest(args: Int, code: String, parse: (args: Array<String>) -> Unit) {
val response = MiraiCodeParser.DynamicParser(args) { args0 -> parse(args0); AtAll }.parse(null, code)
assertNotNull(response, "Parser not invoked")
}
runTest(3, "test,\\,test,\\,\\,test") { (arg1, arg2, arg3) ->
assertEquals("test", arg1)
assertEquals(",test", arg2)
assertEquals(",,test", arg3)
}
runTest(2, ",") {}
}
@Test
fun testCodes() {
assertEquals(AtAll.toMessageChain(), "[mirai:atall]".deserializeMiraiCode())
@ -46,5 +62,16 @@ class TestMiraiCode {
assertEquals(buildMessageChain {
+Dice(1)
}, "[mirai:dice:1]".deserializeMiraiCode())
val musicShare = MusicShare(
kind = MusicKind.NeteaseCloudMusic,
title = "ファッション",
summary = "rinahamu/Yunomi",
jumpUrl = "http://music.163.com/song/1338728297/?userid=324076307",
pictureUrl = "http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg",
musicUrl = "http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307",
brief = "",
)
assertEquals(musicShare.toMessageChain(), musicShare.serializeToMiraiCode().deserializeMiraiCode())
}
}

View File

@ -292,13 +292,12 @@ internal abstract class AbstractBot<N : BotNetworkHandler> constructor(
// https://github.com/mamoe/mirai/issues/1019
kotlin.runCatching {
nick
bot.nick
}.onFailure {
throw contextualBugReportException(
context = "Bot login",
forDebug = it.toString(),
e = it,
)
bot.asQQAndroidBot().nick = MiraiImpl.queryProfile(bot, bot.id).nickname
if (bot.nick.isBlank()) {
logger.warning { "Unable to fetch nickname of bot." }
}
}
logger.info { "Login successful" }

View File

@ -20,7 +20,6 @@ import kotlinx.coroutines.withContext
import kotlinx.io.core.discardExact
import kotlinx.io.core.readBytes
import kotlinx.serialization.json.*
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.*
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.*
@ -794,6 +793,9 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
accept = accept,
blackList = blackList
).sendWithoutExpect()
if (!accept) return@apply
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, "")))
}

View File

@ -156,6 +156,33 @@ private object ReceiveMessageTransformer {
}
}
fun MessageChainBuilder.compressContinuousPlainText() {
var index = 0
val builder = StringBuilder()
while (index + 1 < size) {
val elm0 = get(index)
val elm1 = get(index + 1)
if (elm0 is PlainText && elm1 is PlainText) {
builder.setLength(0)
var end = -1
for (i in index until size) {
val elm = get(i)
if (elm is PlainText) {
end = i
builder.append(elm.content)
} else break
}
set(index, PlainText(builder.toString()))
// do delete
val index1 = index + 1
repeat(end - index) {
removeAt(index1)
}
}
index++
}
}
fun MessageChain.cleanupRubbishMessageElements(): MessageChain {
var previousLast: SingleMessage? = null
var last: SingleMessage? = null
@ -215,15 +242,14 @@ private object ReceiveMessageTransformer {
}
}
if (element is PlainText) { // 处理分片消息
append(element.content)
} else {
add(element)
}
append(element)
previousLast = last
last = element
}
// 处理分片信息
compressContinuousPlainText()
}
}

View File

@ -165,6 +165,8 @@ internal open class QQAndroidClient(
class MessageSvcSyncData {
val firstNotify: AtomicBoolean = atomic(true)
var latestMsgNewGroupTime: Long = currentTimeSeconds()
var latestMsgNewFriendTime: Long = currentTimeSeconds()
@Volatile
var syncCookie: ByteArray? = null
@ -180,12 +182,13 @@ internal open class QQAndroidClient(
val pbGetMessageCacheList = SyncingCacheList<PbGetMessageSyncId>()
internal data class SystemMsgNewGroupSyncId(
internal data class SystemMsgNewSyncId(
val sequence: Long,
val time: Long
)
val systemMsgNewGroupCacheList = SyncingCacheList<SystemMsgNewGroupSyncId>(10)
val systemMsgNewGroupCacheList = SyncingCacheList<SystemMsgNewSyncId>(10)
val systemMsgNewFriendCacheList = SyncingCacheList<SystemMsgNewSyncId>(10)
internal data class PbPushTransMsgSyncId(

View File

@ -19,6 +19,7 @@ import net.mamoe.mirai.event.events.MemberJoinRequestEvent
import net.mamoe.mirai.event.events.NewFriendRequestEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.message.contextualBugReportException
import net.mamoe.mirai.internal.network.MultiPacketByIterable
import net.mamoe.mirai.internal.network.Packet
import net.mamoe.mirai.internal.network.QQAndroidClient
import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg
@ -27,11 +28,12 @@ import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.internal.utils._miraiContentToString
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
import kotlin.math.max
internal class NewContact {
internal object SystemMsgNewFriend :
OutgoingPacketFactory<NewFriendRequestEvent?>("ProfileService.Pb.ReqSystemMsgNew.Friend") {
OutgoingPacketFactory<Packet?>("ProfileService.Pb.ReqSystemMsgNew.Friend") {
operator fun invoke(client: QQAndroidClient) = buildOutgoingUniPacket(client) {
writeProtoBuf(
@ -55,18 +57,40 @@ internal class NewContact {
}
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): NewFriendRequestEvent? {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet? {
readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run {
val struct = friendmsgs.firstOrNull()// 会有重复且无法过滤, 不要用 map
return struct?.msg?.run {
NewFriendRequestEvent(
bot,
struct.msgSeq,
msgAdditional,
struct.reqUin,
groupCode,
reqUinNick
)
return friendmsgs.filter {
it.msgTime >= bot.client.syncingController.latestMsgNewFriendTime
}.mapNotNull { struct ->
if (!bot.client.syncingController.systemMsgNewFriendCacheList.addCache(
QQAndroidClient.MessageSvcSyncData.SystemMsgNewSyncId(
struct.msgSeq,
struct.msgTime
)
)
) { // duplicate
return@mapNotNull null
}
struct.msg?.run {
NewFriendRequestEvent(
bot,
struct.msgSeq,
msgAdditional,
struct.reqUin,
groupCode,
reqUinNick
)
}
}.let { packets ->
when {
packets.isEmpty() -> null
packets.size == 1 -> packets[0]
else -> MultiPacketByIterable(packets)
}
}.also {
bot.client.syncingController.run {
latestMsgNewFriendTime = max(latestMsgNewFriendTime, friendmsgs.maxOfOrNull { it.msgTime } ?: 0)
}
}
}
}
@ -143,18 +167,8 @@ internal class NewContact {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet? {
return readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run {
val struct = groupmsgs.firstOrNull() ?: return null // 会有重复且无法过滤, 不要用 map
if (!bot.client.syncingController.systemMsgNewGroupCacheList.addCache(
QQAndroidClient.MessageSvcSyncData.SystemMsgNewGroupSyncId(struct.msgSeq, struct.msgTime)
)
) { // duplicate
return null
}
struct.msg?.run {
//this.soutv("SystemMsg")
fun handleStruct(struct: Structmsg.StructMsg): Packet? {
return struct.msg?.run {
when (subType) {
1 -> { // 处理被邀请入群 或 处理成员入群申请
when (groupMsgType) {
@ -232,6 +246,33 @@ internal class NewContact {
}
}
}
return readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run {
groupmsgs.filter {
it.msgTime >= bot.client.syncingController.latestMsgNewGroupTime
}.mapNotNull { struct ->
if (!bot.client.syncingController.systemMsgNewGroupCacheList.addCache(
QQAndroidClient.MessageSvcSyncData.SystemMsgNewSyncId(
struct.msgSeq,
struct.msgTime
)
)
) { // duplicate
return@mapNotNull null
}
handleStruct(struct)
}.let { packets ->
when {
packets.isEmpty() -> null
packets.size == 1 -> packets[0]
else -> MultiPacketByIterable(packets)
}
}.also {
bot.client.syncingController.run {
latestMsgNewGroupTime = max(latestMsgNewGroupTime, groupmsgs.maxOfOrNull { it.msgTime } ?: 0)
}
}
}
}
internal object Action : OutgoingPacketFactory<Nothing?>("ProfileService.Pb.ReqSystemMsgAction.Group") {

View File

@ -156,7 +156,11 @@ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("Onlin
) = when {
flags and 16 != 0 -> MemberPermission.ADMINISTRATOR
flags and 8 != 0 -> MemberPermission.OWNER
flags == 0 || flags == 1 -> MemberPermission.MEMBER
(when (flags) {
1, 0, 64,
-> true
else -> false
}) -> MemberPermission.MEMBER
else -> {
bot.logger.warning { "判断群 ${sender.group.id} 的群员 ${sender.id} 的权限失败: ${flags._miraiContentToString()}. 请完整截图或复制此日志并确认其真实权限后发送给 mirai 维护者以帮助解决问题." }
sender.permission