diff --git a/.github/ISSUE_TEMPLATE/bug---.md b/.github/ISSUE_TEMPLATE/bug---.md index 0b9dc5339..d3e26731e 100644 --- a/.github/ISSUE_TEMPLATE/bug---.md +++ b/.github/ISSUE_TEMPLATE/bug---.md @@ -7,7 +7,7 @@ assignees: '' --- -## 问题 + @@ -17,14 +17,18 @@ assignees: '' + ``` -## 如何复现 +### 如何复现 +### 版本 +mirai: `` + diff --git a/build.gradle.kts b/build.gradle.kts index acac7bd82..322996654 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,6 +18,7 @@ buildscript { classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.Kotlin.stdlib}") classpath("org.jetbrains.kotlin:kotlin-serialization:${Versions.Kotlin.stdlib}") classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${Versions.Kotlin.atomicFU}") + classpath("org.jetbrains.kotlinx:binary-compatibility-validator:${Versions.Kotlin.binaryValidator}") } } @@ -26,6 +27,10 @@ plugins { // id("com.jfrog.bintray") version Versions.Publishing.bintray apply false } +// https://github.com/kotlin/binary-compatibility-validator +apply(plugin = "binary-compatibility-validator") + + project.ext.set("isAndroidSDKAvailable", false) // until @@ -139,17 +144,20 @@ subprojects { } -fun Project.findLatestFile(): Map.Entry? { +fun Project.findLatestFile(): Map.Entry { return File(projectDir, "build/libs").walk() .filter { it.isFile } .onEach { println("all files=$it") } - .filter { it.name.matches(Regex("""${project.name}-([0-9]|\.)*\.jar""")) } + .filter { it.name.matches(Regex("""${project.name}-[0-9][0-9]*(\.[0-9]*)*.*\.jar""")) } .onEach { println("matched file: ${it.name}") } .associateBy { it.nameWithoutExtension.substringAfterLast('-') } .onEach { println("versions: $it") } - .maxBy { - it.key.split('.').foldRightIndexed(0) { index: Int, s: String, acc: Int -> - acc + 100.0.pow(2 - index).toInt() * (s.toIntOrNull() ?: 0) + .maxBy { (version, file) -> + version.split('.').let { + if (it.size == 2) it + "0" + else it + }.reversed().foldIndexed(0) { index: Int, acc: Int, s: String -> + acc + 100.0.pow(index).toInt() * (s.toIntOrNull() ?: 0) } - } + } ?: error("cannot find any file to upload") } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index 53cf6548b..a2a8e023d 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -18,6 +18,7 @@ object Versions { const val atomicFU = "0.14.2" const val serialization = "0.20.0" const val ktor = "1.3.2" + const val binaryValidator = "0.2.3" const val io = "0.1.16" const val coroutinesIo = "0.1.16" diff --git a/gradle/api-validation.gradle b/gradle/api-validation.gradle new file mode 100644 index 000000000..c18c40cdc --- /dev/null +++ b/gradle/api-validation.gradle @@ -0,0 +1,13 @@ +apiValidation { + ignoredPackages += [ + "net.mamoe.mirai.event.internal", + "net.mamoe.mirai.utils.internal" + ] + + ignoredPackages += [ + "net.mamoe.mirai.qqandroid.contact", + "net.mamoe.mirai.qqandroid.message", + "net.mamoe.mirai.qqandroid.network", + "net.mamoe.mirai.qqandroid.utils" + ] +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt index 3fd42d44e..7f9da3362 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt @@ -504,8 +504,8 @@ internal class MsgSvc : ProtoBuf { @Serializable internal class SecretFileHead( - @ProtoId(1) @JvmField val secretFileMsg: SubMsgType0xc1.MsgBody? = null, - @ProtoId(2) @JvmField val secretFileStatus: SubMsgType0x1a.MsgBody? = null + @ProtoId(1) @JvmField val secretFileMsg: SubMsgType0xc1.MsgBody? = null + // @ProtoId(2) @JvmField val secretFileStatus: SubMsgType0x1a.MsgBody? = null ) @Serializable @@ -815,6 +815,7 @@ internal class SubMsgType0xc1 { ) : ProtoBuf } +/* @Serializable internal class SubMsgType0x1a { @Serializable @@ -830,4 +831,4 @@ internal class SubMsgType0x1a { @ProtoId(9) @JvmField val fromUin: Long = 0L, @ProtoId(10) @JvmField val toUin: Long = 0L ) : ProtoBuf -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt index d19d4f6f6..208cc38f4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/msgType0x210.kt @@ -515,6 +515,7 @@ internal class Submsgtype0x129 { } +/* internal class Submsgtype0x1a { internal class SubMsgType0x1a : ProtoBuf { @Serializable @@ -531,7 +532,7 @@ internal class Submsgtype0x1a { @ProtoId(10) @JvmField val toUin: Long = 0L ) : ProtoBuf } -} +}*/ internal class Submsgtype0x26 { @@ -2730,6 +2731,7 @@ internal class Submsgtype0xbe { } +/* internal class Submsgtype0xc1 { internal class Submsgtype0xc1 : ProtoBuf { @Serializable @@ -2740,7 +2742,7 @@ internal class Submsgtype0xc1 { ) : ProtoBuf } } - +*/ internal class Submsgtype0xc3 { internal class Submsgtype0xc3 : ProtoBuf { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt index c7b39a789..eafc01a63 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/NewContact.kt @@ -52,7 +52,7 @@ internal class NewContact { struct.msgSeq, msgAdditional, struct.reqUin, - Group.calculateGroupUinByGroupCode(groupCode), + groupCode, reqUinNick ) } @@ -143,7 +143,7 @@ internal class NewContact { bot, struct.msgSeq, actionUin, - Group.calculateGroupUinByGroupCode(groupCode), + groupCode, groupName, actionUinNick ) @@ -155,7 +155,7 @@ internal class NewContact { struct.msgSeq, msgAdditional, struct.reqUin, - Group.calculateGroupUinByGroupCode(groupCode), + groupCode, groupName, reqUinNick ) @@ -185,7 +185,7 @@ internal class NewContact { true -> 11 // accept false -> 12 // reject }, - groupCode = Group.calculateGroupCodeByGroupUin(event.groupId), + groupCode = event.groupId, msg = "", remark = "", blacklist = blackList diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index 556a990ca..e4caae7c5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive import kotlinx.io.core.* import kotlinx.serialization.Serializable +import net.mamoe.mirai.Bot import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.nameCardOrNick @@ -401,13 +402,15 @@ internal class OnlinePush { when { pkg.authorUin == bot.id && operator.id == bot.id -> null else -> { - MessageRecallEvent.GroupRecall(bot, + MessageRecallEvent.GroupRecall( + bot, pkg.authorUin, pkg.seq, pkg.msgRandom, pkg.time, operator, - group) + group + ) } } } @@ -583,6 +586,10 @@ internal class OnlinePush { } ?: emptySequence() } + fun Submsgtype0x27.SubMsgType0x27.ModCustomFace.transform(bot: QQAndroidBot): Sequence = + sequenceOf(BotFaceChangedEvent(Bot.getInstance(uin))) + + return@lambda528 vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.MsgBody.serializer()).msgModInfos.asSequence() .flatMap { when { @@ -590,6 +597,7 @@ internal class OnlinePush { it.msgDelFriend != null -> it.msgDelFriend.transform(bot) it.msgModGroupProfile != null -> it.msgModGroupProfile.transform(bot) it.msgModGroupMemberProfile != null -> it.msgModGroupMemberProfile.transform(bot) + it.msgModCustomFace != null -> it.msgModCustomFace.transform(bot) else -> { bot.network.logger.debug { "Transformers528 0x27L: new data: ${it._miraiContentToString()}" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index e455dd646..ab4a1b724 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -24,6 +24,7 @@ import net.mamoe.mirai.network.closeAndJoin import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.internal.retryCatching import kotlin.coroutines.CoroutineContext +import kotlin.time.Duration import kotlin.time.ExperimentalTime import kotlin.time.measureTime @@ -138,7 +139,7 @@ abstract class BotImpl constructor( reconnect() } - logger.info { "Reconnected successfully in ${time.inMilliseconds} ms" } + logger.info { "Reconnected successfully in ${time.asHumanReadable}" } } is BotOfflineEvent.Active -> { val msg = if (event.cause == null) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribersBuilder.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribersBuilder.kt index 1423915f4..fadf9d1d2 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribersBuilder.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribersBuilder.kt @@ -287,7 +287,7 @@ open class MessageSubscribersBuilder( @MessageDsl fun atAll(): ListeningFilter = content { message.firstIsInstanceOrNull() != null } - /** [消息内容][Message.contentToString]包含 [AtAll] */ + /** [消息内容][Message.contentToString]包含目标为 [target] 的 [At] */ @MessageDsl fun at(target: Long): ListeningFilter = content { message.firstIsInstanceOrNull()?.target == target } @@ -447,4 +447,4 @@ open class MessageSubscribersBuilder( @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE) @DslMarker -annotation class MessageDsl \ No newline at end of file +annotation class MessageDsl diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt index e5edf02fe..e41107f9a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt @@ -88,6 +88,13 @@ data class BotReloginEvent( val cause: Throwable? ) : BotEvent, BotActiveEvent, AbstractEvent() +/** + * [Bot] 头像被修改(通过其他客户端修改了Bot的头像) + */ +data class BotFaceChangedEvent( + override val bot: Bot +) : BotEvent, Packet, AbstractEvent() + // endregion // region 消息 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TimeUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TimeUtils.kt index c31e03724..b66bc6e1d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TimeUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TimeUtils.kt @@ -15,6 +15,10 @@ package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic +import kotlin.math.floor +import kotlin.time.Duration +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime /** * 时间戳 @@ -76,4 +80,21 @@ inline val Int.weeksToSeconds: Long @get:JvmSynthetic inline val Int.monthsToSeconds: Long - get() = this * 30.daysToSeconds \ No newline at end of file + get() = this * 30.daysToSeconds + +@ExperimentalTime +val Duration.asHumanReadable: String + get() { + val builder = StringBuilder() + val days = toInt(DurationUnit.DAYS) + val hours = toInt(DurationUnit.HOURS) % 24 + val minutes = toInt(DurationUnit.MINUTES) % 60 + val s = floor(toDouble(DurationUnit.SECONDS) % 60 * 1000) / 1000 + with(builder) { + if (days != 0) append("${days}d ") + if (hours != 0) append("${hours}h ") + if (minutes != 0) append("${minutes}min ") + append("${s}s") + } + return builder.toString() + } diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/TimeTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/TimeTest.kt new file mode 100644 index 000000000..ea40ae231 --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/utils/TimeTest.kt @@ -0,0 +1,31 @@ +/* + * 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.utils + +import kotlin.test.Test +import kotlin.test.assertTrue +import kotlin.time.DurationUnit +import kotlin.time.ExperimentalTime +import kotlin.time.toDuration + +internal class TimeTest { + @ExperimentalTime + @Test + fun testTimeHumanReadable() { + val time0 = 1.toDuration(DurationUnit.DAYS) + + 20.toDuration(DurationUnit.HOURS) + + 15.toDuration(DurationUnit.MINUTES) + + 2057.toDuration(DurationUnit.MILLISECONDS) + println(time0.asHumanReadable) + assertTrue { time0.asHumanReadable == "1d 20h 15min 2.057s" } + val time1 = 1.toDuration(DurationUnit.DAYS) + 59.toDuration(DurationUnit.MINUTES) + println(time1.asHumanReadable) + assertTrue { time1.asHumanReadable == "1d 59min 0.0s" } + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/DeferredReusableInput.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/DeferredReusableInput.jvm.kt index 974acb445..ab41a7cea 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/DeferredReusableInput.jvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/DeferredReusableInput.jvm.kt @@ -24,8 +24,8 @@ internal actual class DeferredReusableInput actual constructor( is InputStream -> strategy.newImageCache(input) is ByteArray -> strategy.newImageCache(input) is Input -> strategy.newImageCache(input) - is BufferedImage -> strategy.newImageCache(input, extraArg as String) is URL -> strategy.newImageCache(input) + is BufferedImage -> strategy.newImageCache(input, extraArg as String) else -> error("Internal error: unsupported DeferredReusableInput.input: ${input::class.qualifiedName}") }.input }