diff --git a/CHANGELOG.md b/CHANGELOG.md index 474c6b4a9..9f930d186 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,7 +101,7 @@ #### `OfflineMessageSource` 构造 可使用 DSL 构造离线消息, 修改其发送人, 发送时间, 发送内容等. 这对于跨群转发等情况十分有用. -[OfflineMessageSource.kt: Line 90](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/OfflineMessageSource.kt#L90) +[MessageSourceBuilder.kt: Line 90](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSourceBuilder.kt#L90) DSL 总览: ``` val source: OfflineMessageSource = bot.buildMessageSource { diff --git a/build.gradle.kts b/build.gradle.kts index 7d2ef354c..acac7bd82 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ import kotlin.math.pow buildscript { repositories { mavenLocal() - maven(url = "https://mirrors.huaweicloud.com/repository/maven") + // maven(url = "https://mirrors.huaweicloud.com/repository/maven") maven(url = "https://dl.bintray.com/kotlin/kotlin-eap") jcenter() google() @@ -51,7 +51,7 @@ allprojects { version = Versions.Mirai.version repositories { - maven(url = "https://mirrors.huaweicloud.com/repository/maven") + // maven(url = "https://mirrors.huaweicloud.com/repository/maven") maven(url = "https://dl.bintray.com/kotlin/kotlin-eap") jcenter() google() diff --git a/buildSrc/src/main/kotlin/Versions.kt b/buildSrc/src/main/kotlin/Versions.kt index d48b87a6b..5d39b82fa 100644 --- a/buildSrc/src/main/kotlin/Versions.kt +++ b/buildSrc/src/main/kotlin/Versions.kt @@ -24,6 +24,8 @@ object Versions { const val dokka = "0.10.1" } + const val jcekt = "1.0.0" + object Android { const val androidGradlePlugin = "3.5.3" } diff --git a/mirai-core-qqandroid/build.gradle.kts b/mirai-core-qqandroid/build.gradle.kts index e579b4b46..2778938e5 100644 --- a/mirai-core-qqandroid/build.gradle.kts +++ b/mirai-core-qqandroid/build.gradle.kts @@ -51,6 +51,7 @@ kotlin { api(kotlin("stdlib", Versions.Kotlin.stdlib)) api(kotlinx("serialization-runtime-common", Versions.Kotlin.serialization)) api(kotlinx("serialization-protobuf-common", Versions.Kotlin.serialization)) + api("moe.him188:jcekt-common:${Versions.jcekt}") api("org.jetbrains.kotlinx:atomicfu:${Versions.Kotlin.atomicFU}") api(kotlinx("io", Versions.Kotlin.io)) api(kotlinx("coroutines-io", Versions.Kotlin.coroutinesIo)) @@ -86,6 +87,7 @@ kotlin { dependencies { runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE // api(kotlinx("coroutines-debug", "1.3.5")) + api("moe.him188:jcekt:${Versions.jcekt}") api(kotlinx("serialization-runtime", Versions.Kotlin.serialization)) //api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization)) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt index dbef3f8c5..668dbcbbd 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.common.kt @@ -15,7 +15,6 @@ import io.ktor.client.HttpClient import io.ktor.client.request.* import io.ktor.client.request.forms.MultiPartFormDataContent import io.ktor.client.request.forms.formData -import io.ktor.client.statement.HttpResponse import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.async import kotlinx.coroutines.io.ByteReadChannel @@ -51,7 +50,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils import net.mamoe.mirai.qqandroid.utils.encodeToString import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray -import net.mamoe.mirai.qqandroid.utils.toReadPacket import net.mamoe.mirai.utils.* import kotlin.collections.asSequence import kotlin.contracts.ExperimentalContracts @@ -190,7 +188,7 @@ internal class QQAndroidBot constructor( } } - group.checkBotPermissionOperator() + group.checkBotPermission(MemberPermission.ADMINISTRATOR) } override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) { @@ -351,10 +349,6 @@ internal abstract class QQAndroidBotBase constructor( return sequence } - override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult { - TODO("not implemented") - } - @Suppress("RemoveExplicitTypeArguments") // false positive override suspend fun recall(source: MessageSource) { check(source is MessageSourceInternal) @@ -375,7 +369,7 @@ internal abstract class QQAndroidBotBase constructor( else -> error("stub") } if (this.id != source.fromId) { - group.checkBotPermissionOperator() + group.checkBotPermission(MemberPermission.ADMINISTRATOR) } MessageRecallEvent.GroupRecall( this, @@ -679,8 +673,8 @@ internal abstract class QQAndroidBotBase constructor( response.proto.uint32UpIp.zip(response.proto.uint32UpPort), response.proto.msgSig, MiraiPlatformUtils.md5(body), - body.toReadPacket(), - body.size.toLong().and(0xFFFF_FFFF), // don't use toLongUnsigned: Overload resolution ambiguity + @Suppress("INVISIBLE_REFERENCE") + net.mamoe.mirai.utils.internal.asReusableInput0(body), // don't use toLongUnsigned: Overload resolution ambiguity "group long message", 27 ) @@ -766,13 +760,6 @@ internal abstract class QQAndroidBotBase constructor( } } - @Suppress("DeprecatedCallableAddReplaceWith") - @PlannedRemoval("1.0.0") - @Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING) - override suspend fun openChannel(image: Image): ByteReadChannel { - return MiraiPlatformUtils.Http.get(queryImageUrl(image)).content.toKotlinByteReadChannel() - } - /** * 获取 获取群公告 所需的 bkn 参数 * */ diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt index 75d86d63f..86f7f7426 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/FriendImpl.kt @@ -87,6 +87,10 @@ internal class FriendImpl( @JvmSynthetic @OptIn(MiraiInternalAPI::class, ExperimentalStdlibApi::class, ExperimentalTime::class) override suspend fun uploadImage(image: ExternalImage): Image = try { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + if (image.input is net.mamoe.mirai.utils.internal.DeferredReusableInput) { + image.input.init(bot.configuration.fileCacheStrategy) + } if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } @@ -96,14 +100,15 @@ internal class FriendImpl( srcUin = bot.id.toInt(), dstUin = id.toInt(), fileId = 0, - fileMd5 = image.md5, - fileSize = image.inputSize.toInt(), - fileName = image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName, + fileMd5 = @Suppress("INVISIBLE_MEMBER") image.md5, + fileSize = @Suppress("INVISIBLE_MEMBER") + image.input.size.toInt(), + fileName = @Suppress("INVISIBLE_MEMBER") image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName, imgOriginal = 1 ) ).sendAndExpect() - @Suppress("UNCHECKED_CAST", "DEPRECATION") // bug + @Suppress("UNCHECKED_CAST", "DEPRECATION", "INVISIBLE_MEMBER") return when (response) { is LongConn.OffPicUp.Response.FileExists -> net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId) .also { @@ -111,7 +116,7 @@ internal class FriendImpl( } is LongConn.OffPicUp.Response.RequireUpload -> { bot.network.logger.verbose { - "[Http] Uploading friend image, size=${image.inputSize.sizeToString()}" + "[Http] Uploading friend image, size=${image.input.size.sizeToString()}" } val time = measureTime { @@ -120,13 +125,12 @@ internal class FriendImpl( bot.id, null, imageInput = image.input, - inputSize = image.inputSize, uKeyHex = response.uKey.toUHexString("") ) } bot.network.logger.verbose { - "[Http] Uploading friend image: succeed at ${(image.inputSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" + "[Http] Uploading friend image: succeed at ${(image.input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" } /* @@ -151,6 +155,7 @@ internal class FriendImpl( } } } finally { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") (image.input as? Closeable)?.close() } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt index da8048897..3de8f7418 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/GroupImpl.kt @@ -109,7 +109,8 @@ internal class GroupImpl( override var name: String get() = _name set(newValue) { - checkBotPermissionOperator() + + checkBotPermission(MemberPermission.ADMINISTRATOR) if (_name != newValue) { val oldValue = _name _name = newValue @@ -131,7 +132,7 @@ internal class GroupImpl( override var entranceAnnouncement: String get() = _announcement set(newValue) { - checkBotPermissionOperator() + checkBotPermission(MemberPermission.ADMINISTRATOR) if (_announcement != newValue) { val oldValue = _announcement _announcement = newValue @@ -152,7 +153,7 @@ internal class GroupImpl( override var isAllowMemberInvite: Boolean get() = _allowMemberInvite set(newValue) { - checkBotPermissionOperator() + checkBotPermission(MemberPermission.ADMINISTRATOR) if (_allowMemberInvite != newValue) { val oldValue = _allowMemberInvite _allowMemberInvite = newValue @@ -186,7 +187,8 @@ internal class GroupImpl( override var isConfessTalkEnabled: Boolean get() = _confessTalk set(newValue) { - checkBotPermissionOperator() + + checkBotPermission(MemberPermission.ADMINISTRATOR) if (_confessTalk != newValue) { val oldValue = _confessTalk _confessTalk = newValue @@ -207,7 +209,8 @@ internal class GroupImpl( override var isMuteAll: Boolean get() = _muteAll set(newValue) { - checkBotPermissionOperator() + + checkBotPermission(MemberPermission.ADMINISTRATOR) if (_muteAll != newValue) { val oldValue = _muteAll _muteAll = newValue @@ -403,6 +406,10 @@ internal class GroupImpl( @OptIn(ExperimentalTime::class) @JvmSynthetic override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage = try { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + if (image.input is net.mamoe.mirai.utils.internal.DeferredReusableInput) { + image.input.init(bot.configuration.fileCacheStrategy) + } if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } @@ -412,7 +419,7 @@ internal class GroupImpl( uin = bot.id, groupCode = id, md5 = image.md5, - size = image.inputSize + size = image.input.size.toInt() ).sendAndExpect() @Suppress("UNCHECKED_CAST") // bug @@ -432,7 +439,7 @@ internal class GroupImpl( bot, response.uploadIpList.zip(response.uploadPortList), response.uKey, - image, + image.input, kind = "group image", commandId = 2 ) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt index a5f0350cf..495bb45e1 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/MemberImpl.kt @@ -105,7 +105,7 @@ internal class MemberImpl constructor( get() = _nameCard set(newValue) { if (id != bot.id) { - group.checkBotPermissionOperator() + group.checkBotPermission(MemberPermission.ADMINISTRATOR) } if (_nameCard != newValue) { val oldValue = _nameCard @@ -118,7 +118,7 @@ internal class MemberImpl constructor( newValue ).sendWithoutExpect() } - MemberCardChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast() + MemberCardChangeEvent(oldValue, newValue, this@MemberImpl).broadcast() } } } @@ -205,6 +205,7 @@ internal class MemberImpl constructor( check(response.success) { "kick failed: ${response.ret}" } + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") group.members.delegate.removeIf { it.id == this@MemberImpl.id } MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast() } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt index 862a7cd4c..83564cbca 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/contact/util.kt @@ -59,17 +59,17 @@ internal fun Contact.logMessageSent(message: Message) { } @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) -internal fun ContactMessage.logMessageReceived() { +internal fun MessageEvent.logMessageReceived() { when (this) { - is GroupMessage -> bot.logger.verbose { + is GroupMessageEvent -> bot.logger.verbose { "[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(${sender.id}) -> ${message.toString() .singleLine()}" } - is TempMessage -> bot.logger.verbose { + is TempMessageEvent -> bot.logger.verbose { "[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(Temp ${sender.id}) -> ${message.toString() .singleLine()}" } - is FriendMessage -> bot.logger.verbose { + is FriendMessageEvent -> bot.logger.verbose { "${sender.nick.singleLine()}(${sender.id}) -> ${message.toString().singleLine()}" } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt index 39836158a..56867c3ba 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/convension.kt @@ -358,10 +358,10 @@ internal fun List.joinToMessageChain(groupIdOrZero: Long, bot: B ="" a_actionData="" url=""/> */ /** - * [JsonMessage] + * json? */ 1 -> @Suppress("DEPRECATION_ERROR") - list.add(JsonMessage(content)) + list.add(ServiceMessage(1, content)) /** * [LongMessage], [ForwardMessage] */ @@ -381,7 +381,7 @@ internal fun List.joinToMessageChain(groupIdOrZero: Long, bot: B else -> { if (element.richMsg.serviceId == 60 || content.startsWith(">? = LockFreeLinkedList() @@ -491,7 +495,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo is Packet.NoLog -> { // nothing to do } - is ContactMessage -> packet.logMessageReceived() + is MessageEvent -> packet.logMessageReceived() is Event -> bot.logger.verbose { "Event: ${packet.toString().singleLine()}" } else -> logger.verbose { "Packet: ${packet.toString().singleLine()}" } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index 181ebf761..d2fae3d0c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -77,6 +77,10 @@ internal open class QQAndroidClient( val device: DeviceInfo = SystemDeviceInfo(context), bot: QQAndroidBot ) { + @Suppress("INVISIBLE_MEMBER") + val subAppId: Long + get() = bot.configuration.protocol.id + internal val serverList: MutableList> = DefaultServerList.toMutableList() val keys: Map by lazy { @@ -368,7 +372,7 @@ internal class WLoginSigInfo( val deviceToken: ByteArray ) { override fun toString(): String { - return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=${psKeyMap.toString()}, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})" + return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})" } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt index 548bac1e8..6db8c41af 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + package net.mamoe.mirai.qqandroid.network.highway import io.ktor.client.HttpClient @@ -20,22 +22,24 @@ import io.ktor.utils.io.ByteWriteChannel import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.delay import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.io.ByteReadChannel import kotlinx.coroutines.isActive import kotlinx.coroutines.withTimeoutOrNull -import kotlinx.io.InputStream -import kotlinx.io.core.Input import kotlinx.io.core.discardExact -import kotlinx.io.core.readAvailable import kotlinx.io.core.use import kotlinx.serialization.InternalSerializationApi import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead -import net.mamoe.mirai.qqandroid.utils.* +import net.mamoe.mirai.qqandroid.utils.PlatformSocket +import net.mamoe.mirai.qqandroid.utils.SocketException +import net.mamoe.mirai.qqandroid.utils.addSuppressedMirai import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.utils.io.withUse -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.internal.ReusableInput +import net.mamoe.mirai.utils.verbose import kotlin.coroutines.EmptyCoroutineContext import kotlin.math.roundToInt import kotlin.time.ExperimentalTime @@ -47,8 +51,7 @@ internal suspend fun HttpClient.postImage( htcmd: String, uin: Long, groupcode: Long?, - imageInput: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor - inputSize: Long, + imageInput: ReusableInput, uKeyHex: String ): Boolean = post { url { @@ -63,7 +66,7 @@ internal suspend fun HttpClient.postImage( parameters["term"] = "pc" parameters["ver"] = "5603" - parameters["filesize"] = inputSize.toString() + parameters["filesize"] = imageInput.size.toString() parameters["range"] = 0.toString() parameters["ukey"] = uKeyHex @@ -72,63 +75,46 @@ internal suspend fun HttpClient.postImage( body = object : OutgoingContent.WriteChannelContent() { override val contentType: ContentType = ContentType.Image.Any - override val contentLength: Long = inputSize + override val contentLength: Long = imageInput.size @OptIn(MiraiExperimentalAPI::class) override suspend fun writeTo(channel: ByteWriteChannel) { - ByteArrayPool.useInstance { buffer: ByteArray -> - when (imageInput) { - is Input -> { - var size: Int - while (imageInput.readAvailable(buffer).also { size = it } > 0) { - channel.writeFully(buffer, 0, size) - channel.flush() - } - } - is ByteReadChannel -> imageInput.copyAndClose(channel) - is InputStream -> { - var size: Int - while (imageInput.read(buffer).also { size = it } > 0) { - channel.writeFully(buffer, 0, size) - channel.flush() - } - } - else -> error("unsupported imageInput: ${imageInput::class.simpleName}") - } - } + imageInput.writeTo(channel) + } } } == HttpStatusCode.OK @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) internal object HighwayHelper { + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") suspend fun uploadImageToServers( bot: QQAndroidBot, servers: List>, uKey: ByteArray, - image: ExternalImage, + image: ReusableInput, kind: String, commandId: Int - ) = uploadImageToServers(bot, servers, uKey, image.md5, image.input, image.inputSize, kind, commandId) + ) = uploadImageToServers(bot, servers, uKey, image.md5, image, kind, commandId) + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @OptIn(ExperimentalTime::class) suspend fun uploadImageToServers( bot: QQAndroidBot, servers: List>, uKey: ByteArray, md5: ByteArray, - input: Any, - inputSize: Long, + input: ReusableInput, kind: String, commandId: Int ) = servers.retryWithServers( - (inputSize * 1000 / 1024 / 10).coerceAtLeast(5000), + (input.size * 1000 / 1024 / 10).coerceAtLeast(5000), onFail = { throw IllegalStateException("cannot upload $kind, failed on all servers.", it) } ) { ip, port -> bot.network.logger.verbose { - "[Highway] Uploading $kind to ${ip}:$port, size=${inputSize.sizeToString()}" + "[Highway] Uploading $kind to ${ip}:$port, size=${input.size.sizeToString()}" } val time = measureTime { @@ -137,7 +123,6 @@ internal object HighwayHelper { serverIp = ip, serverPort = port, imageInput = input, - inputSize = inputSize.toInt(), fileMd5 = md5, ticket = uKey, commandId = commandId @@ -145,22 +130,21 @@ internal object HighwayHelper { } bot.network.logger.verbose { - "[Highway] Uploading $kind: succeed at ${(inputSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" + "[Highway] Uploading $kind: succeed at ${(input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s" } } + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @OptIn(InternalCoroutinesApi::class) internal suspend fun uploadImage( client: QQAndroidClient, serverIp: String, serverPort: Int, ticket: ByteArray, - imageInput: Any, - inputSize: Int, + imageInput: ReusableInput, fileMd5: ByteArray, commandId: Int // group=2, friend=1 ) { - require(imageInput is Input || imageInput is InputStream || imageInput is ByteReadChannel) { "unsupported imageInput: ${imageInput::class.simpleName}" } require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" } // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } // require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" } @@ -177,22 +161,24 @@ internal object HighwayHelper { socket.use { createImageDataPacketSequence( client = client, + appId = client.subAppId.toInt(), command = "PicUp.DataUp", commandId = commandId, ticket = ticket, data = imageInput, - dataSize = inputSize, fileMd5 = fileMd5 - ).collect { - socket.send(it) - //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 + ).withUse { + flow.collect { + socket.send(it) + //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 - socket.read().withUse { - discardExact(1) - val headLength = readInt() - discardExact(4) - val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) - check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" } + socket.read().withUse { + discardExact(1) + val headLength = readInt() + discardExact(4) + val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) + check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" } + } } } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt index 2329335a2..6a8e174c6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt @@ -11,12 +11,7 @@ package net.mamoe.mirai.qqandroid.network.highway -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.io.ByteReadChannel -import kotlinx.io.InputStream import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.Input import kotlinx.io.core.buildPacket import kotlinx.io.core.writeFully import kotlinx.serialization.InternalSerializationApi @@ -25,41 +20,34 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.utils.ByteArrayPool import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils -import net.mamoe.mirai.qqandroid.utils.io.chunkedFlow import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.internal.ChunkedFlowSession +import net.mamoe.mirai.utils.internal.ChunkedInput +import net.mamoe.mirai.utils.internal.ReusableInput +import net.mamoe.mirai.utils.internal.map @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) internal fun createImageDataPacketSequence( // RequestDataTrans client: QQAndroidClient, command: String, - appId: Int = 537062845, + appId: Int, dataFlag: Int = 4096, commandId: Int, localId: Int = 2052, ticket: ByteArray, - - data: Any, - dataSize: Int, + data: ReusableInput, fileMd5: ByteArray, sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE -): Flow { +): ChunkedFlowSession { ByteArrayPool.checkBufferSize(sizePerPacket) - require(data is Input || data is InputStream || data is ByteReadChannel) { "unsupported data: ${data::class.simpleName}" } // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } - require(data !is ByteReadPacket || data.remaining.toInt() == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as ByteReadPacket).remaining}" } - val flow = when (data) { - is ByteReadPacket -> data.chunkedFlow(sizePerPacket) - is Input -> data.chunkedFlow(sizePerPacket) - is ByteReadChannel -> data.chunkedFlow(sizePerPacket) - is InputStream -> data.chunkedFlow(sizePerPacket) - else -> error("unreachable code") - } + val session: ChunkedFlowSession = data.chunkedFlow(sizePerPacket) var offset = 0L - return flow.map { chunkedInput -> + return session.map { chunkedInput -> buildPacket { val head = CSDataHighwayHead.ReqDataHighwayHead( msgBasehead = CSDataHighwayHead.DataHighwayHead( @@ -82,7 +70,7 @@ internal fun createImageDataPacketSequence( // cacheAddr = 812157193, datalength = chunkedInput.bufferSize, dataoffset = offset, - filesize = dataSize.toLong(), + filesize = data.size, serviceticket = ticket, md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize), fileMd5 = fileMd5, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt index c1f86c96d..296a697d2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt @@ -10,9 +10,9 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt index b78e6b4d0..58d2267e4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt index 73def5748..aaf198b5a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/GroupMngReq.kt @@ -1,8 +1,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt index 7f62e4ad7..72cba5272 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/MsgType0x210.kt @@ -1,9 +1,9 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt index 5359dc44a..6dad2bb9d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField internal class OnlinePushPack { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt index 2b827da3b..dec460165 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt @@ -10,10 +10,10 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Suppress("ArrayInDataClass") diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt index 474ecffe1..e9ebbf658 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestMSFForceOffline.kt @@ -1,8 +1,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt index bb8d0b2b3..eba2a751c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt @@ -10,9 +10,9 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField private val EMPTY_MAP = mapOf() diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt index a8c31cefe..4a3f05ea3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt index fb44f55e1..24147c7a2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt index ed692d803..c87f3f9e6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.qqandroid.network.protocol.data.jce import kotlinx.serialization.Serializable +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId import kotlin.jvm.JvmField @Serializable diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt index 8a792cae5..a1ae4a7b7 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt @@ -197,6 +197,7 @@ internal object KnownPacketFactories { readString(readInt() - 4)// uinAccount + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") ByteArrayPool.useInstance(this.remaining.toInt()) { data -> val size = this.readAvailable(data) @@ -219,6 +220,7 @@ internal object KnownPacketFactories { it as IncomingPacket if (it.packetFactory is IncomingPacketFactory && it.packetFactory.canBeCached && bot.network.pendingEnabled) { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") bot.network.pendingIncomingPackets?.addLast(it.also { it.consumer = consumer it.flag2 = flag2 @@ -384,6 +386,7 @@ internal object KnownPacketFactories { } 0 -> { val data = if (bot.client.loginState == 0) { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") ByteArrayPool.useInstance(this.remaining.toInt()) { byteArrayBuffer -> val size = (this.remaining - 1).toInt() this.readFully(byteArrayBuffer, 0, size) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt index 38b0dc56c..36181578d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt @@ -83,7 +83,7 @@ internal fun BytePacketBuilder.t18( @OptIn(MiraiInternalAPI::class) internal fun BytePacketBuilder.t106( appId: Long = 16L, - subAppId: Long = 537062845L, + subAppId: Long, appClientVersion: Int = 0, uin: Long, n5_always_1: Int = 1, @@ -159,7 +159,7 @@ internal fun BytePacketBuilder.t116( internal fun BytePacketBuilder.t100( appId: Long = 16, - subAppId: Long = 537062845, + subAppId: Long, appClientVersion: Int ) { writeShort(0x100) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt index 0f3fc8474..6f3e15648 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt @@ -100,7 +100,7 @@ internal class TroopManagement { serviceType = 7, result = 0, bodybuffer = Oidb0x88d.ReqBody( - appid = 537062845, + appid = client.subAppId.toInt(), stzreqgroupinfo = listOf( Oidb0x88d.ReqGroupInfo( stgroupinfo = Oidb0x88d.GroupInfo( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt index fdbbf0c21..81ca1a4b5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt @@ -17,6 +17,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x388 import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.toLongUnsigned import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf import kotlin.random.Random @@ -27,9 +28,6 @@ internal fun getRandomString(length: Int): String = private val defaultRanges: Array = arrayOf('a'..'z', 'A'..'Z', '0'..'9') -internal fun getRandomString(length: Int, charRange: CharRange): String = - String(CharArray(length) { charRange.random() }) - internal fun getRandomString(length: Int, vararg charRanges: CharRange): String = String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() }) @@ -41,7 +39,7 @@ internal class ImgStore { uin: Long, groupCode: Long, md5: ByteArray, - size: Long, + size: Int, picWidth: Int = 0, // not orthodox picHeight: Int = 0, // not orthodox picType: Int = 1000, @@ -63,7 +61,7 @@ internal class ImgStore { groupCode = groupCode, srcUin = uin, fileMd5 = md5, - fileSize = size, + fileSize = size.toLongUnsigned(), fileId = fileId, fileName = filename, picWidth = picWidth, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index 21831738d..5337fba75 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -30,8 +30,8 @@ import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.getFriendOrNull -import net.mamoe.mirai.message.FriendMessage -import net.mamoe.mirai.message.TempMessage +import net.mamoe.mirai.message.FriendMessageEvent +import net.mamoe.mirai.message.TempMessageEvent import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.PttMessage import net.mamoe.mirai.message.data.Voice @@ -228,6 +228,7 @@ internal class MessageSvc { // 新群 val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") bot.groups.delegate.addLast(newGroup) return@mapNotNull BotJoinGroupEvent(newGroup) } else { @@ -237,6 +238,7 @@ internal class MessageSvc { return@mapNotNull null } + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo()) .also { group.members.delegate.addLast(it) }) } @@ -250,6 +252,7 @@ internal class MessageSvc { if (group.members.contains(msg.msgHead.authUin)) { return@mapNotNull null } + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo()) .also { group.members.delegate.addLast(it) }) } @@ -276,7 +279,7 @@ internal class MessageSvc { friend.lastMessageSequence.loop { instant -> if (msg.msgHead.msgSeq > instant) { if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) { - return@mapNotNull FriendMessage( + return@mapNotNull FriendMessageEvent( friend, msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true), msg.msgHead.msgTime @@ -307,7 +310,7 @@ internal class MessageSvc { member.lastMessageSequence.loop { instant -> if (msg.msgHead.msgSeq > instant) { if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) { - return@mapNotNull TempMessage( + return@mapNotNull TempMessageEvent( member, msg.toMessageChain( bot, 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 ff096f338..556a990ca 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 @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive @@ -23,7 +23,7 @@ import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getGroupOrNull -import net.mamoe.mirai.message.GroupMessage +import net.mamoe.mirai.message.GroupMessageEvent import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.contact.* import net.mamoe.mirai.qqandroid.message.contextualBugReportException @@ -87,7 +87,8 @@ internal class OnlinePush { } } - val group = bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群 + val group = + bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群 val sender = if (anonymous != null) { group.newAnonymous(anonymous.anonNick.encodeToString()) } else { @@ -108,12 +109,12 @@ internal class OnlinePush { } val flags = extraInfo?.flags ?: 0 - return GroupMessage( + return GroupMessageEvent( senderName = name.also { if (it != sender.nameCard) { val origin = sender._nameCard sender._nameCard = name - MemberCardChangeEvent(origin, name, sender, sender).broadcast() // 不知道operator + MemberCardChangeEvent(origin, name, sender).broadcast() } }, sender = sender, @@ -562,7 +563,7 @@ internal class OnlinePush { if (new == old) return@mapNotNull null member._nameCard = new - return@mapNotNull MemberCardChangeEvent(old, new, member, null) + return@mapNotNull MemberCardChangeEvent(old, new, member) } 2 -> { if (info.value.singleOrNull()?.toInt() != 0) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/ProfileService.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/ProfileService.kt index b4ec39361..4a147633f 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/ProfileService.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/ProfileService.kt @@ -24,11 +24,8 @@ import net.mamoe.mirai.qqandroid.utils.io.serialization.jceRequestSBuffer import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket import net.mamoe.mirai.qqandroid.utils.io.serialization.writeJceStruct import net.mamoe.mirai.qqandroid.utils.toByteArray -import net.mamoe.mirai.utils.SinceMirai internal class ProfileService { - - @SinceMirai("0.37.0") object GroupMngReq : OutgoingPacketFactory("ProfileService.GroupMngReq") { data class GroupMngReqResponse(val errorCode: Int, val errorMessage: String) : Packet diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt index ae668d57a..2aa225d6b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/ConfigPushSvc.kt @@ -70,6 +70,7 @@ internal class ConfigPushSvc { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse? { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") ByteArrayPool.useInstance(this.remaining.toInt()) { buffer -> val length = this.readAvailable(buffer) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Heartbeat.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Heartbeat.kt index 0582d698a..4d2ed0ff6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Heartbeat.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/Heartbeat.kt @@ -25,7 +25,7 @@ internal class Heartbeat { operator fun invoke( client: QQAndroidClient ): OutgoingPacket = buildLoginOutgoingPacket(client, 0, key = NO_ENCRYPT) { - writeSsoPacket(client, 537062845, commandName, sequenceId = it) { + writeSsoPacket(client, client.subAppId, commandName, sequenceId = it) { } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt index 3b5ed64f4..52733ddf2 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt @@ -10,6 +10,7 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.login import kotlinx.io.core.ByteReadPacket +import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.network.Packet @@ -88,8 +89,6 @@ internal class StatSvc { override fun toString(): String = "Response(StatSvc.register)" } - private const val subAppId = 537062845L - @OptIn(MiraiInternalAPI::class) operator fun invoke( client: QQAndroidClient, @@ -101,7 +100,7 @@ internal class StatSvc { key = client.wLoginSigInfo.d2Key ) { sequenceId -> writeSsoPacket( - client, subAppId = subAppId, commandName = commandName, + client, subAppId = client.subAppId, commandName = commandName, extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId ) { writeJceStruct( @@ -153,7 +152,7 @@ internal class StatSvc { var44.strVendorName = ROMUtil.getRomName(); var44.strVendorOSName = ROMUtil.getRomVersion(20); */ - bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump( + bytes_0x769_reqbody = ProtoBuf.dump( Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody( rpt_config_list = listOf( Oidb0x769.ConfigSeq( diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt index 70c7d7fca..dbf39123a 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt @@ -32,8 +32,6 @@ internal class WtLogin { @Suppress("FunctionName") @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) internal object Login : OutgoingPacketFactory("wtlogin.login") { - private const val subAppId = 537062845L - /** * 提交验证码 */ @@ -42,7 +40,7 @@ internal class WtLogin { client: QQAndroidClient, ticket: String ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> - writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { + writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(2) // subCommand writeShort(4) // count of TLVs @@ -59,7 +57,7 @@ internal class WtLogin { captchaSign: ByteArray, captchaAnswer: String ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> - writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { + writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(2) // subCommand writeShort(4) // count of TLVs @@ -78,7 +76,7 @@ internal class WtLogin { client: QQAndroidClient, t402: ByteArray ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> - writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { + writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(20) // subCommand writeShort(4) // count of TLVs, probably ignored by server? @@ -100,7 +98,7 @@ internal class WtLogin { ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> writeSsoPacket( client, - subAppId, + client.subAppId, commandName, sequenceId = sequenceId, unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00" @@ -126,13 +124,12 @@ internal class WtLogin { */ object SubCommand9 { private const val appId = 16L - private const val subAppId = 537062845L @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) operator fun invoke( client: QQAndroidClient ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> - writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { + writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(9) // subCommand writeShort(17) // count of TLVs, probably ignored by server? @@ -142,7 +139,7 @@ internal class WtLogin { t1(client.uin, client.device.ipAddress) t106( appId, - subAppId /* maybe 1*/, + client.subAppId /* maybe 1*/, client.appClientVersion, client.uin, 1, @@ -166,7 +163,7 @@ internal class WtLogin { if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L) */ t116(client.miscBitMap, client.subSigMap) - t100(appId, subAppId, client.appClientVersion) + t100(appId, client.subAppId, client.appClientVersion) t107(0) // t108(byteArrayOf()) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/ByteArrayPool.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/ByteArrayPool.kt index 586633752..37c296940 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/ByteArrayPool.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/ByteArrayPool.kt @@ -1,41 +1,9 @@ package net.mamoe.mirai.qqandroid.utils -import kotlinx.io.pool.DefaultPool import kotlinx.io.pool.ObjectPool /** * 缓存 [ByteArray] 实例的 [ObjectPool] */ -internal object ByteArrayPool : DefaultPool(256) { - /** - * 每一个 [ByteArray] 的大小 - */ - const val BUFFER_SIZE: Int = 8192 * 8 - - override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE) - - override fun clearInstance(instance: ByteArray): ByteArray = instance - - fun checkBufferSize(size: Int) { - require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } - } - - fun checkBufferSize(size: Long) { - require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } - } - - /** - * 请求一个大小至少为 [requestedSize] 的 [ByteArray] 实例. - */ // 不要写为扩展函数. 它需要优先于 kotlinx.io 的扩展函数 resolve - inline fun useInstance(requestedSize: Int = 0, block: (ByteArray) -> R): R { - if (requestedSize > BUFFER_SIZE) { - return ByteArray(requestedSize).run(block) - } - val instance = borrow() - try { - return block(instance) - } finally { - recycle(instance) - } - } -} \ No newline at end of file +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +internal typealias ByteArrayPool = net.mamoe.mirai.utils.internal.ByteArrayPool \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt index a267e5b73..0b374df63 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/input.kt @@ -27,6 +27,7 @@ import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") internal inline fun ByteReadPacket.useBytes( n: Int = remaining.toInt(),//not that safe but adequate block: (data: ByteArray, length: Int) -> R diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/IOFormat.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/IOFormat.kt deleted file mode 100644 index 642cad758..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/IOFormat.kt +++ /dev/null @@ -1,14 +0,0 @@ -package net.mamoe.mirai.qqandroid.utils.io.serialization - -import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerialFormat -import kotlinx.serialization.SerializationStrategy - -internal interface IOFormat : SerialFormat { - - fun dumpTo(serializer: SerializationStrategy, ojb: T, output: Output) - - fun load(deserializer: DeserializationStrategy, input: Input): T -} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/JceOld.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/JceOld.kt deleted file mode 100644 index b850ffa56..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/JceOld.kt +++ /dev/null @@ -1,850 +0,0 @@ -/* - * 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.qqandroid.utils.io.serialization - -import kotlinx.io.charsets.Charset -import kotlinx.io.core.* -import kotlinx.serialization.* -import kotlinx.serialization.builtins.ByteArraySerializer -import kotlinx.serialization.builtins.MapEntrySerializer -import kotlinx.serialization.builtins.SetSerializer -import kotlinx.serialization.internal.* -import kotlinx.serialization.modules.EmptyModule -import kotlinx.serialization.modules.SerialModule -import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf -import net.mamoe.mirai.qqandroid.utils.io.readString -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.BYTE -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.DOUBLE -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.FLOAT -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.INT -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.JCE_MAX_STRING_LENGTH -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.LIST -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.LONG -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.MAP -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.SHORT -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.SIMPLE_LIST -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRING1 -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRING4 -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRUCT_BEGIN -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRUCT_END -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.ZERO_TYPE -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceHead -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId -import net.mamoe.mirai.qqandroid.utils.toReadPacket - -@PublishedApi -internal val CharsetGBK = Charset.forName("GBK") - -@PublishedApi -internal val CharsetUTF8 = Charset.forName("UTF8") - -internal enum class JceCharset(val kotlinCharset: Charset) { - GBK(Charset.forName("GBK")), - UTF8(Charset.forName("UTF8")) -} - -internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation(index)?.id - -/** - * Jce 数据结构序列化和反序列化工具, 能将 kotlinx.serialization 通用的注解标记格式的 `class` 序列化为 [ByteArray] - */ -@Suppress("DEPRECATION_ERROR") -@OptIn(InternalSerializationApi::class) -internal class JceOld private constructor(private val charset: JceCharset, override val context: SerialModule = EmptyModule) : - SerialFormat, BinaryFormat { - - private inner class ListWriter( - private val count: Int, - private val tag: Int, - private val parentEncoder: JceEncoder - ) : JceEncoder(BytePacketBuilder()) { - override fun SerialDescriptor.getTag(index: Int): Int { - return 0 - } - - override fun endEncode(descriptor: SerialDescriptor) { - parentEncoder.writeHead(LIST, this.tag) - parentEncoder.encodeTaggedInt(0, count) - parentEncoder.output.writePacket(this.output.build()) - } - } - - private inner class JceMapWriter( - output: BytePacketBuilder - ) : JceEncoder(output) { - override fun SerialDescriptor.getTag(index: Int): Int { - return if (index % 2 == 0) 0 else 1 - } - - /* - override fun endEncode(desc: SerialDescriptor) { - parentEncoder.writeHead(MAP, this.tag) - parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count) - // println(this.output.toByteArray().toUHexString()) - parentEncoder.output.write(this.output.toByteArray()) - }*/ - - override fun beginCollection( - descriptor: SerialDescriptor, - collectionSize: Int, - vararg typeSerializers: KSerializer<*> - ): CompositeEncoder { - return this - } - - override fun beginStructure( - descriptor: SerialDescriptor, - vararg typeSerializers: KSerializer<*> - ): CompositeEncoder { - return this - } - } - - /** - * From: com.qq.taf.jce.JceOutputStream - */ - @Suppress("unused", "MemberVisibilityCanBePrivate") - @OptIn(ExperimentalIoApi::class) - private open inner class JceEncoder( - internal val output: BytePacketBuilder - ) : TaggedEncoder() { - override val context get() = this@JceOld.context - - override fun SerialDescriptor.getTag(index: Int): Int { - return getSerialId(this, index) ?: error("cannot find @SerialId") - } - - /** - * 序列化最开始的时候的 - */ - override fun beginStructure( - descriptor: SerialDescriptor, - vararg typeSerializers: KSerializer<*> - ): CompositeEncoder = - when (descriptor.kind) { - StructureKind.LIST -> this - StructureKind.MAP -> this - StructureKind.CLASS, StructureKind.OBJECT -> this - is PolymorphicKind -> this - else -> throw SerializationException("Primitives are not supported at top-level") - } - - @OptIn(ImplicitReflectionSerializer::class) - @Suppress("UNCHECKED_CAST", "NAME_SHADOWING") - override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) = when { - serializer.descriptor.kind == StructureKind.MAP -> { - try { - val entries = (value as Map<*, *>).entries - val serializer = (serializer as MapLikeSerializer) - val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) - - this.writeHead(MAP, currentTag) - this.encodeTaggedInt(0, entries.count()) - SetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries) - } catch (e: Exception) { - super.encodeSerializableValue(serializer, value) - } - } - serializer.descriptor.kind == StructureKind.LIST - && value is ByteArray -> encodeTaggedByteArray(popTag(), value as ByteArray) - serializer.descriptor.kind == StructureKind.LIST - && serializer.descriptor.getElementDescriptor(0) is PrimitiveKind -> { - serializer.serialize( - ListWriter( - when (value) { - is ShortArray -> value.size - is IntArray -> value.size - is LongArray -> value.size - is FloatArray -> value.size - is DoubleArray -> value.size - is CharArray -> value.size - is ByteArray -> value.size - is BooleanArray -> value.size - else -> error("unknown array type: ${value.getClassName()}") - }, popTag(), this - ), - value - ) - } - serializer.descriptor.kind == StructureKind.LIST && value is Array<*> -> { - if (serializer.descriptor.getElementDescriptor(0).kind is PrimitiveKind.BYTE) { - encodeTaggedByteArray(popTag(), (value as Array).toByteArray()) - } else - serializer.serialize( - ListWriter((value as Array<*>).size, popTag(), this), - value - ) - } - serializer.descriptor.kind == StructureKind.LIST -> { - serializer.serialize( - ListWriter((value as Collection<*>).size, popTag(), this), - value - ) - } - else -> { - if (value is JceStruct) { - if (currentTagOrNull == null) { - serializer.serialize(this, value) - } else { - this.writeHead(STRUCT_BEGIN, popTag()) - serializer.serialize(JceEncoder(this.output), value) - this.writeHead(STRUCT_END, 0) - } - } else if (value is ProtoBuf) { - this.encodeTaggedByteArray(popTag(), ProtoBufWithNullableSupport.dump(value)) - } else { - serializer.serialize(this, value) - } - } - } - - override fun encodeTaggedByte(tag: Int, value: Byte) { - if (value.toInt() == 0) { - writeHead(ZERO_TYPE, tag) - } else { - writeHead(BYTE, tag) - output.writeByte(value) - } - } - - override fun encodeTaggedShort(tag: Int, value: Short) { - if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) { - encodeTaggedByte(tag, value.toByte()) - } else { - writeHead(SHORT, tag) - output.writeShort(value) - } - } - - override fun encodeTaggedInt(tag: Int, value: Int) { - if (value in Short.MIN_VALUE..Short.MAX_VALUE) { - encodeTaggedShort(tag, value.toShort()) - } else { - writeHead(INT, tag) - output.writeInt(value) - } - } - - override fun encodeTaggedFloat(tag: Int, value: Float) { - writeHead(FLOAT, tag) - output.writeFloat(value) - } - - override fun encodeTaggedDouble(tag: Int, value: Double) { - writeHead(DOUBLE, tag) - output.writeDouble(value) - } - - override fun encodeTaggedLong(tag: Int, value: Long) { - if (value in Int.MIN_VALUE..Int.MAX_VALUE) { - encodeTaggedInt(tag, value.toInt()) - } else { - writeHead(LONG, tag) - output.writeLong(value) - } - } - - override fun encodeTaggedBoolean(tag: Int, value: Boolean) { - encodeTaggedByte(tag, if (value) 1 else 0) - } - - override fun encodeTaggedChar(tag: Int, value: Char) { - encodeTaggedByte(tag, value.toByte()) - } - - override fun encodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor, ordinal: Int) { - encodeTaggedInt(tag, ordinal) - } - - override fun encodeTaggedNull(tag: Int) { - } - - override fun encodeTaggedUnit(tag: Int) { - encodeTaggedNull(tag) - } - - fun encodeTaggedByteArray(tag: Int, bytes: ByteArray) { - writeHead(SIMPLE_LIST, tag) - writeHead(BYTE, 0) - encodeTaggedInt(0, bytes.size) - output.writeFully(bytes) - } - - override fun encodeTaggedString(tag: Int, value: String) { - require(value.length <= JCE_MAX_STRING_LENGTH) { "string is too long for tag $tag" } - val array = value.toByteArray(charset.kotlinCharset) - if (array.size > 255) { - writeHead(STRING4, tag) - output.writeInt(array.size) - output.writeFully(array) - } else { - writeHead(STRING1, tag) - output.writeByte(array.size.toByte()) // one byte - output.writeFully(array) - } - } - - override fun encodeTaggedValue(tag: Int, value: Any) { - when (value) { - is Byte -> encodeTaggedByte(tag, value) - is Short -> encodeTaggedShort(tag, value) - is Int -> encodeTaggedInt(tag, value) - is Long -> encodeTaggedLong(tag, value) - is Float -> encodeTaggedFloat(tag, value) - is Double -> encodeTaggedDouble(tag, value) - is Boolean -> encodeTaggedBoolean(tag, value) - is String -> encodeTaggedString(tag, value) - is Unit -> { - } - else -> error("unsupported type: ${value.getClassName()}") - } - } - - @PublishedApi - internal fun writeHead(type: Byte, tag: Int) { - if (tag < 15) { - this.output.writeByte(((tag shl 4) or type.toInt()).toByte()) - return - } - if (tag < 256) { - this.output.writeByte((type.toInt() or 0xF0).toByte()) - this.output.writeByte(tag.toByte()) - return - } - error("tag is too large: $tag") - } - } - - private open inner class JceMapReader( - val size: Int, - input: JceInput - ) : JceDecoder(input) { - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - return size - } - - override fun SerialDescriptor.getTag(index: Int): Int { - // 奇数 0, 即 key; 偶数 1, 即 value - return if (index % 2 == 0) 0 else 1 - } - } - - private open inner class JceListReader( - val size: Int, - input: JceInput - ) : JceDecoder(input) { - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - return size - } - - override fun SerialDescriptor.getTag(index: Int): Int { - return 0 - } - } - - private open inner class JceStructReader( - input: JceInput - ) : JceDecoder(input) { - override fun endStructure(descriptor: SerialDescriptor) { - - } - } - - private open inner class NullReader( - input: JceInput - ) : JceDecoder(input) - - private open inner class JceDecoder( - internal val input: JceInput - ) : TaggedDecoder() { - override fun SerialDescriptor.getTag(index: Int): Int { - return getSerialId(this, index) ?: error("cannot find tag with index $index") - } - - override fun decodeTaggedByte(tag: Int): Byte = input.readByte(tag) - override fun decodeTaggedShort(tag: Int): Short = input.readShort(tag) - override fun decodeTaggedInt(tag: Int): Int = input.readInt(tag) - override fun decodeTaggedLong(tag: Int): Long = input.readLong(tag) - override fun decodeTaggedFloat(tag: Int): Float = input.readFloat(tag) - override fun decodeTaggedDouble(tag: Int): Double = input.readDouble(tag) - override fun decodeTaggedChar(tag: Int): Char = input.readByte(tag).toChar() - override fun decodeTaggedString(tag: Int): String = input.readString(tag) - override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag) - - override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int { - return input.readInt(tag) - } - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - return 0 - } - - /** - * 在 [KSerializer.serialize] 前 - */ - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - //// println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}") - when { - // 由于 Byte 的数组有两种方式写入, 需特定读取器 - descriptor.kind == StructureKind.LIST - && descriptor.getElementDescriptor(0).kind == PrimitiveKind.BYTE -> { - // ByteArray, 交给 decodeSerializableValue 进行处理 - return this - } - descriptor.kind == StructureKind.LIST -> { - // if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) { - // // Array - // return this // 交给 decodeSerializableValue - // } - - val tag = currentTagOrNull - @Suppress("SENSELESS_COMPARISON") // 推断 bug - if (tag != null && input.skipToTagOrNull(tag) { - popTag() - if (it.type == SIMPLE_LIST) { - input.readHead() // list 里面元素类型, 没必要知道 - } - return when (it.type) { - SIMPLE_LIST, LIST -> JceListReader(input.readInt(0), this.input) - MAP -> JceMapReader(input.readInt(0), this.input) - else -> error("type mismatch") - } - } == null && descriptor.isNullable) { - return NullReader(this.input) - } - } - - descriptor.kind == StructureKind.MAP -> { - val tag = currentTagOrNull - if (tag != null) { - popTag() - } - return JceMapReader(input.readInt(0), this.input) - } - } - - val tag = currentTagOrNull - val jceHead = input.peakHeadOrNull() - if (tag != null && (jceHead == null || jceHead.tag > tag)) { - return NullReader(this.input) - } - - return super.beginStructure(descriptor, *typeParams) - } - - override fun decodeTaggedNull(tag: Int): Nothing? { - return null - } - - override fun decodeTaggedNotNullMark(tag: Int): Boolean { - return !isTagMissing(tag) - } - - fun isTagMissing(tag: Int): Boolean { - val head = input.peakHeadOrNull() - return input.isEndOfInput || head == null || head.tag > tag - } - - @Suppress("UNCHECKED_CAST") - override fun decodeNullableSerializableValue(deserializer: DeserializationStrategy): T? { - // - println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}") - if (deserializer is NullReader) { - return null - } - currentTagOrNull?.let { - if (this.isTagMissing(it)) { - return null - } - } - when { - deserializer.descriptor == ByteArraySerializer().descriptor -> { - val tag = popTag() - return if (isTagMissing(tag)) input.readByteArrayOrNull(tag) as? T - else input.readByteArray(tag) as T - } - deserializer.descriptor.kind == StructureKind.LIST -> { - if (deserializer is ReferenceArraySerializer<*, *> - && (deserializer as ListLikeSerializer).typeParams.isNotEmpty() - && (deserializer as ListLikeSerializer).typeParams[0] is ByteSerializer - ) { - val tag = popTag() - return if (isTagMissing(tag)) input.readByteArrayOrNull(tag)?.toTypedArray() as? T - else input.readByteArray(tag).toTypedArray() as T - } else if (deserializer is ArrayListSerializer<*> - && (deserializer as ArrayListSerializer<*>).typeParams.isNotEmpty() - && (deserializer as ArrayListSerializer<*>).typeParams[0] is ByteSerializer - ) { - val tag = popTag() - return if (isTagMissing(tag)) input.readByteArrayOrNull(tag)?.toMutableList() as? T - else input.readByteArray(tag).toMutableList() as T - } - val tag = currentTag -// // println(tag) - @Suppress("SENSELESS_COMPARISON") // false positive - if (input.skipToTagOrNull(tag) { - return deserializer.deserialize(JceListReader(input.readInt(0), input)) - } == null) { - if (isTagMissing(tag)) { - return null - } else error("property is notnull but cannot find tag $tag") - } - error("UNREACHABLE CODE") - } - deserializer.descriptor.kind == StructureKind.MAP -> { - val tag = popTag() - @Suppress("SENSELESS_COMPARISON") - if (input.skipToTagOrNull(tag) { head -> - check(head.type == MAP) { "type mismatch: ${head.type}" } - // 将 mapOf(k1 to v1, k2 to v2, ...) 转换为 listOf(k1, v1, k2, v2, ...) 以便于写入. - val serializer = (deserializer as MapLikeSerializer) - val mapEntrySerial = - MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) - val setOfEntries = - SetSerializer(mapEntrySerial).deserialize(JceMapReader(input.readInt(0), input)) - return setOfEntries.associateBy({ it.key }, { it.value }) as T - } == null) { - if (isTagMissing(tag)) { - return null - } else error("property is notnull but cannot find tag $tag") - } - error("UNREACHABLE CODE") - } - } - - if (deserializer.descriptor.kind == StructureKind.CLASS || deserializer.descriptor.kind == StructureKind.OBJECT) { - val tag = currentTagOrNull - if (tag != null) { - @Suppress("SENSELESS_COMPARISON") // 推断 bug - if (input.skipToTagOrNull(tag) { - check(it.type == STRUCT_BEGIN) { "type mismatch: ${it.type}" } - //popTag() - return deserializer.deserialize(JceStructReader(input)).also { - while (input.input.canRead() && input.peakHeadOrNull()?.type != STRUCT_END) { - input.readHeadOrNull() ?: return@also - } - input.readHeadOrNull() - } - } == null && isTagMissing(tag)) { - return null - } else error("cannot find tag $tag") - } - - return deserializer.deserialize(JceDecoder(this.input)) - } - - val tag = currentTagOrNull ?: return deserializer.deserialize(JceDecoder(this.input)) - return if (!this.isTagMissing(tag)) { - try { - deserializer.deserialize(this) - } catch (e: Exception) { - println("exception when tag=$tag") - throw e - } - } else { - // popTag() - null - } - } - - @Suppress("UNCHECKED_CAST") - override fun decodeSerializableValue(deserializer: DeserializationStrategy): T { - return decodeNullableSerializableValue(deserializer as DeserializationStrategy) as? T - ?: error("value with tag $currentTagOrNull(by ${deserializer.getClassName()}) is not optional but cannot find. currentJceHead = ${input.currentJceHead}") - } - } - - - @OptIn(ExperimentalUnsignedTypes::class) - internal inner class JceInput( - @PublishedApi - internal val input: ByteReadPacket, - maxReadSize: Long = input.remaining - ) : Closeable { - private val leastRemaining = input.remaining - maxReadSize - internal val isEndOfInput: Boolean get() = input.remaining <= leastRemaining - - internal var currentJceHead: JceHead? = input.doReadHead() - - override fun close() = input.close() - - internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull() - - @PublishedApi - internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head") - - @PublishedApi - internal fun readHeadOrNull(): JceHead? = input.doReadHead() - - /** - * 读取下一个 head 存储到 [currentJceHead] - */ - private fun ByteReadPacket.doReadHead(): JceHead? { - if (isEndOfInput) { - currentJceHead = null - // println("doReadHead: endOfInput") - return null - } - val var2 = readUByte() - val type = var2 and 15u - var tag = var2.toUInt() shr 4 - if (tag == 15u) { - if (isEndOfInput) { - currentJceHead = null - // println("doReadHead: endOfInput2") - return null - } - tag = readUByte().toUInt() - } - currentJceHead = JceHead( - tag = tag.toInt(), - type = type.toByte() - ) - // println("doReadHead: $currentJceHead") - return currentJceHead - } - - fun readBoolean(tag: Int): Boolean = - readBooleanOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readByte(tag: Int): Byte = - readByteOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readShort(tag: Int): Short = - readShortOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - fun readLong(tag: Int): Long = - readLongOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readFloat(tag: Int): Float = - readFloatOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readDouble(tag: Int): Double = - readDoubleOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readString(tag: Int): String = - readStringOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readByteArray(tag: Int): ByteArray = - readByteArrayOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") - - fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) { - when (it.type) { - LIST -> ByteArray(readInt(0)) { readByte(0) } - SIMPLE_LIST -> { - val head = readHead() - readHead() - check(head.type.toInt() == 0) { "type mismatch, expected=0(Byte), got=${head.type}" } - input.readBytes(readInt(0)) - } - else -> error("type mismatch, expected=9(List), got=${it.type}") - } - } - - private fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head -> - return when (head.type) { - STRING1 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset) - STRING4 -> input.readString( - input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, - charset = charset.kotlinCharset - ) - else -> error("type mismatch: ${head.type}, expecting 6 or 7 (for string)") - } - } - - private fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) { - return when (it.type) { - ZERO_TYPE -> 0 - BYTE -> input.readByte().toLong() - SHORT -> input.readShort().toLong() - INT -> input.readInt().toLong() - LONG -> input.readLong() - else -> error("type mismatch ${it.type} when reading tag $tag") - } - } - - private fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0 - 0 -> input.readByte().toShort() - 1 -> input.readShort() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0 - 0 -> input.readByte().toInt() - 1 -> input.readShort().toInt() - 2 -> input.readInt() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0 - 0 -> input.readByte() - else -> error("type mismatch") - } - } - - private fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0f - 4 -> input.readFloat() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) { - return when (it.type.toInt()) { - 12 -> 0.0 - 4 -> input.readFloat().toDouble() - 5 -> input.readDouble() - else -> error("type mismatch: ${it.type}") - } - } - - private fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 } - - - private fun skipField() { - skipField(readHead().type) - } - - private fun skipToStructEnd() { - var head: JceHead - do { - head = readHead() - skipField(head.type) - } while (head.type.toInt() != 11) - } - - @OptIn(ExperimentalUnsignedTypes::class) - @PublishedApi - internal fun skipField(type: Byte) = when (type.toInt()) { - 0 -> this.input.discardExact(1) - 1 -> this.input.discardExact(2) - 2 -> this.input.discardExact(4) - 3 -> this.input.discardExact(8) - 4 -> this.input.discardExact(4) - 5 -> this.input.discardExact(8) - 6 -> this.input.discardExact(this.input.readUByte().toInt()) - 7 -> this.input.discardExact(this.input.readInt()) - 8 -> { // map - repeat(this.readInt(0) * 2) { - skipField() - } - } - 9 -> { // list - repeat(this.readInt(0)) { - skipField() - } - } - 10 -> this.skipToStructEnd() - 11, 12 -> { - - } - 13 -> { - val head = readHead() - check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type } - this.input.discardExact(this.readInt(0)) - } - else -> error("invalid type: $type") - } - - } - - @Suppress("MemberVisibilityCanBePrivate") - companion object { - val UTF8 = - JceOld(JceCharset.UTF8) - val GBK = - JceOld(JceCharset.GBK) - - fun byCharSet(c: JceCharset): JceOld { - return if (c == JceCharset.UTF8) { - UTF8 - } else { - GBK - } - } - - private fun Any?.getClassName(): String = - (if (this == null) Unit::class else this::class).qualifiedName?.split(".")?.takeLast(2)?.joinToString(".") - ?: "" - } - - fun dumpAsPacket(serializer: SerializationStrategy, obj: T): ByteReadPacket { - val encoder = BytePacketBuilder() - val dumper = JceEncoder(encoder) - dumper.encode(serializer, obj) - return encoder.build() - } - - override fun dump(serializer: SerializationStrategy, value: T): ByteArray { - return dumpAsPacket(serializer, value).readBytes() - } - - /** - * 注意 close [packet]!! - */ - fun load( - deserializer: DeserializationStrategy, - packet: ByteReadPacket, - length: Int = packet.remaining.toInt() - ): T { - return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer) - } - - override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T { - return bytes.toReadPacket().use { - val decoder = JceDecoder(JceInput(it)) - decoder.decode(deserializer) - } - } -} - -internal inline fun JceOld.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? { - // println("skipping to $tag start") - while (true) { - if (isEndOfInput) { // 读不了了 - currentJceHead = null - // println("skipping to $tag: endOfInput") - return null - } - - var head = currentJceHead - if (head == null) { // 没有新的 head 了 - head = readHeadOrNull() ?: return null - } - - if (head.tag > tag) { - // println("skipping to $tag: head.tag > tag") - return null - } - // readHead() - if (head.tag == tag) { - // readHeadOrNull() - currentJceHead = null - // println("skipping to $tag: run block") - return block(head) - } - - // println("skipping to $tag: tag not matching") - // println("skipping to $tag: skipField") - this.skipField(head.type) - currentJceHead = readHeadOrNull() - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt index 66d45fad8..565030251 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/ProtoBufWithNullableSupport.kt @@ -23,10 +23,13 @@ import kotlinx.serialization.modules.SerialModule import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoType +import moe.him188.jcekt.JceId import net.mamoe.mirai.qqandroid.utils.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint internal typealias ProtoDesc = Pair +internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation(index)?.id + internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefault: Boolean = false): ProtoDesc { val idx = getSerialId(desc, index) ?: (if (zeroBasedDefault) index else index + 1) val format = desc.findAnnotation(index)?.type diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/README.md b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/README.md deleted file mode 100644 index c82a41a5c..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# io.serialization - -**序列化支持** - -包含: -- QQ 的 JceStruct 相关的全自动序列化和反序列化: [Jce.kt](jce/JceNew.kt) -- Protocol Buffers 的 optional 支持: [ProtoBufWithNullableSupport.kt](ProtoBufWithNullableSupport.kt) - -其中, `ProtoBufWithNullableSupport` 的绝大部分源码来自 `kotlinx.serialization`. 原著权归该项目作者所有. -Mirai 所做的修改已经标记上了 `MIRAI MODIFY START` \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceDecoder.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceDecoder.kt deleted file mode 100644 index 58c2b7dbd..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceDecoder.kt +++ /dev/null @@ -1,330 +0,0 @@ -/* - * 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 - */ - -@file:Suppress("PrivatePropertyName") - -package net.mamoe.mirai.qqandroid.utils.io.serialization.jce - -import kotlinx.serialization.* -import kotlinx.serialization.builtins.AbstractDecoder -import kotlinx.serialization.internal.TaggedDecoder -import kotlinx.serialization.modules.SerialModule - - -@OptIn(InternalSerializationApi::class) // 将来 kotlinx 修改后再复制过来 mirai. -internal class JceDecoder( - val jce: JceInput, override val context: SerialModule -) : TaggedDecoder() { - override val updateMode: UpdateMode - get() = UpdateMode.BANNED - - override fun SerialDescriptor.getTag(index: Int): JceTag { - val annotations = this.getElementAnnotations(index) - - val id = annotations.filterIsInstance().single().id - // ?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}") - //println("getTag: ${this.getElementName(index)}=$id") - - return JceTagCommon(id) - } - - private fun SerialDescriptor.getJceTagId(index: Int): Int { - // higher performance, don't use filterIsInstance - val annotation = getElementAnnotations(index).firstOrNull { it is JceId } - ?: error("missing @JceId for ${getElementName(index)} in ${this.serialName}") - return (annotation as JceId).id - - } - - private val SimpleByteArrayReader: SimpleByteArrayReaderImpl = SimpleByteArrayReaderImpl() - - private inner class SimpleByteArrayReaderImpl : AbstractDecoder() { - override fun decodeSequentially(): Boolean = true - - override fun endStructure(descriptor: SerialDescriptor) { - this@JceDecoder.endStructure(descriptor) - } - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - this@JceDecoder.pushTag(JceTagListElement) - return this@JceDecoder.beginStructure(descriptor, *typeParams) - } - - override fun decodeByte(): Byte = jce.input.readByte() - override fun decodeShort(): Short = error("illegal access") - override fun decodeInt(): Int = error("illegal access") - override fun decodeLong(): Long = error("illegal access") - override fun decodeFloat(): Float = error("illegal access") - override fun decodeDouble(): Double = error("illegal access") - override fun decodeBoolean(): Boolean = error("illegal access") - override fun decodeChar(): Char = error("illegal access") - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = error("illegal access") - override fun decodeString(): String = error("illegal access") - - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - error("should not be reached") - } - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - // 不要读下一个 head - return jce.currentHead.let { jce.readJceIntValue(it) } - } - } - - private val ListReader: ListReaderImpl = ListReaderImpl() - - private inner class ListReaderImpl : AbstractDecoder() { - override fun decodeSequentially(): Boolean = true - override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("should not be reached") - override fun endStructure(descriptor: SerialDescriptor) { - this@JceDecoder.endStructure(descriptor) - } - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - this@JceDecoder.pushTag(JceTagListElement) - - return this@JceDecoder.beginStructure(descriptor, *typeParams) - } - - override fun decodeByte(): Byte = jce.useHead { jce.readJceByteValue(it) } - override fun decodeShort(): Short = jce.useHead { jce.readJceShortValue(it) } - override fun decodeInt(): Int = jce.useHead { jce.readJceIntValue(it) } - override fun decodeLong(): Long = jce.useHead { jce.readJceLongValue(it) } - override fun decodeFloat(): Float = jce.useHead { jce.readJceFloatValue(it) } - override fun decodeDouble(): Double = jce.useHead { jce.readJceDoubleValue(it) } - override fun decodeBoolean(): Boolean = jce.useHead { jce.readJceBooleanValue(it) } - override fun decodeChar(): Char = decodeByte().toChar() - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt() - override fun decodeString(): String = jce.useHead { jce.readJceStringValue(it) } - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - //println("decodeCollectionSize: ${descriptor.serialName}") - // 不读下一个 head - return jce.useHead { jce.readJceIntValue(it) } - } - } - - - private val MapReader: MapReaderImpl = MapReaderImpl() - - private inner class MapReaderImpl : AbstractDecoder() { - override fun decodeSequentially(): Boolean = true - override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("stub") - - override fun endStructure(descriptor: SerialDescriptor) { - this@JceDecoder.endStructure(descriptor) - } - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - println { "MapReader.beginStructure: ${jce.currentHead}" } - this@JceDecoder.pushTag( - when (jce.currentHead.tag) { - 0 -> JceTagMapEntryKey - 1 -> JceTagMapEntryValue - else -> error("illegal map entry head: ${jce.currentHead.tag}") - } - ) - return this@JceDecoder.beginStructure(descriptor, *typeParams) - } - - override fun decodeByte(): Byte = jce.useHead { jce.readJceByteValue(it) } - override fun decodeShort(): Short = jce.useHead { jce.readJceShortValue(it) } - override fun decodeInt(): Int = jce.useHead { jce.readJceIntValue(it) } - override fun decodeLong(): Long = jce.useHead { jce.readJceLongValue(it) } - override fun decodeFloat(): Float = jce.useHead { jce.readJceFloatValue(it) } - override fun decodeDouble(): Double = jce.useHead { jce.readJceDoubleValue(it) } - - override fun decodeBoolean(): Boolean = jce.useHead { jce.readJceBooleanValue(it) } - override fun decodeChar(): Char = decodeByte().toChar() - override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt() - override fun decodeString(): String = jce.useHead { jce.readJceStringValue(it) } - - override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { - println { "decodeCollectionSize in MapReader: ${descriptor.serialName}" } - // 不读下一个 head - return jce.useHead { jce.readJceIntValue(it) } - } - } - - - override fun endStructure(descriptor: SerialDescriptor) { - structureHierarchy-- - println { "endStructure: ${descriptor.serialName}" } - if (currentTagOrNull?.isSimpleByteArray == true) { - jce.prepareNextHead() // read to next head - } - if (descriptor.kind == StructureKind.CLASS) { - if (currentTagOrNull == null) { - return - } - while (true) { - val currentHead = jce.currentHeadOrNull ?: return - if (currentHead.type == Jce.STRUCT_END) { - jce.prepareNextHead() - //println("current end") - break - } - //println("current $currentHead") - jce.skipField(currentHead.type) - jce.prepareNextHead() - } - // pushTag(JceTag(0, true)) - // skip STRUCT_END - // popTag() - } - } - - - companion object { - @Suppress("MemberVisibilityCanBePrivate") - var debuggingMode: Boolean = false - - var structureHierarchy: Int = 0 - - inline fun println(value: () -> String) { - if (debuggingMode) { - kotlin.io.println(" ".repeat(structureHierarchy) + value()) - } - } - - @Suppress("NOTHING_TO_INLINE") - inline fun println(value: Any? = "") { - if (debuggingMode) { - kotlin.io.println(" ".repeat(structureHierarchy) + value) - } - } - } - - override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { - println() - println { "beginStructure: ${descriptor.serialName}" } - structureHierarchy++ - return when (descriptor.kind) { - is PrimitiveKind -> this@JceDecoder - - StructureKind.MAP -> { - //println("!! MAP") - val tag = popTag() - return jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { - it.checkType(Jce.MAP, "beginStructure", tag, descriptor) - MapReader - } - } - StructureKind.LIST -> { - //println("!! ByteArray") - //println("decoderTag: $currentTagOrNull") - //println("jceHead: " + jce.currentHeadOrNull) - return jce.skipToHeadAndUseIfPossibleOrFail(currentTag.id) { - // don't check type. it's polymorphic - - //println("listHead: $it") - when (it.type) { - Jce.SIMPLE_LIST -> { - currentTag.isSimpleByteArray = true - jce.nextHead() // 无用的元素类型 - SimpleByteArrayReader - } - Jce.LIST -> ListReader - else -> error("type mismatch. Expected SIMPLE_LIST or LIST, got $it instead") - } - } - } - StructureKind.CLASS -> { - currentTagOrNull ?: return this@JceDecoder // outermost - - //println("!! CLASS") - //println("decoderTag: $currentTag") - //println("jceHead: " + jce.currentHeadOrNull) - val tag = popTag() - return jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jceHead -> - jceHead.checkType(Jce.STRUCT_BEGIN, "beginStructure", tag, descriptor) - - repeat(descriptor.elementsCount) { - pushTag(descriptor.getTag(descriptor.elementsCount - it - 1)) // better performance - } - this // independent tag stack - } - } - - StructureKind.OBJECT -> error("unsupported StructureKind.OBJECT: ${descriptor.serialName}") - is UnionKind -> error("unsupported UnionKind: ${descriptor.serialName}") - is PolymorphicKind -> error("unsupported PolymorphicKind: ${descriptor.serialName}") - } - } - - override fun decodeSequentially(): Boolean = false - override fun decodeElementIndex(descriptor: SerialDescriptor): Int { - var jceHead = jce.currentHeadOrNull ?: kotlin.run { - println("decodeElementIndex: currentHead == null") - return CompositeDecoder.READ_DONE - } - - println { "decodeElementIndex: ${jce.currentHead}" } - while (!jce.input.endOfInput) { - if (jceHead.type == Jce.STRUCT_END) { - println { "decodeElementIndex: ${jce.currentHead}" } - return CompositeDecoder.READ_DONE - } - - repeat(descriptor.elementsCount) { - val tag = descriptor.getJceTagId(it) - if (tag == jceHead.tag) { - println { - "name=" + descriptor.getElementName( - it - ) - } - return it - } - } - - jce.skipField(jceHead.type) - if (!jce.prepareNextHead()) { - println { "decodeElementIndex EOF" } - break - } - jceHead = jce.currentHead - println { "next! $jceHead" } - } - - return CompositeDecoder.READ_DONE // optional support - } - - override fun decodeTaggedInt(tag: JceTag): Int = - kotlin.runCatching { jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceIntValue(it) } }.getOrElse { - throw IllegalStateException("$tag", it) - } - - override fun decodeTaggedByte(tag: JceTag): Byte = - kotlin.runCatching { jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceByteValue(it) } }.getOrElse { - throw IllegalStateException("$tag", it) - } - - override fun decodeTaggedBoolean(tag: JceTag): Boolean = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceBooleanValue(it) } - - override fun decodeTaggedFloat(tag: JceTag): Float = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceFloatValue(it) } - - override fun decodeTaggedDouble(tag: JceTag): Double = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceDoubleValue(it) } - - override fun decodeTaggedShort(tag: JceTag): Short = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceShortValue(it) } - - override fun decodeTaggedLong(tag: JceTag): Long = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceLongValue(it) } - - override fun decodeTaggedString(tag: JceTag): String = - jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceStringValue(it) } - - override fun decodeTaggedNotNullMark(tag: JceTag): Boolean { - return jce.skipToHeadOrNull(tag.id) != null - } -} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceInput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceInput.kt deleted file mode 100644 index ffdd2e54a..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceInput.kt +++ /dev/null @@ -1,263 +0,0 @@ -/* - * 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.qqandroid.utils.io.serialization.jce - -import kotlinx.io.core.* -import net.mamoe.mirai.qqandroid.utils.io.readString -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset -import net.mamoe.mirai.qqandroid.utils.toUHexString - - -/** - * Jce Input. 需要手动管理 head. - */ -internal class JceInput( - val input: Input, val charset: JceCharset -) { - private var _head: JceHead? = null - - val currentHead: JceHead get() = _head ?: throw EOFException("No current JceHead available") - val currentHeadOrNull: JceHead? get() = _head - - init { - prepareNextHead() - } - - /** - * 读取下一个 [JceHead] 并保存. 可通过 [currentHead] 获取这个 [JceHead]. - * - * @return 是否成功读取. 返回 `false` 时代表 [Input.endOfInput] - */ - fun prepareNextHead(): Boolean { - return readNextHeadButDoNotAssignTo_Head().also { _head = it; } != null - } - - fun nextHead(): JceHead { - if (!prepareNextHead()) { - throw EOFException("No more JceHead available") - } - return currentHead - } - - /** - * 直接读取下一个 [JceHead] 并返回. - * 返回 `null` 则代表 [Input.endOfInput] - */ - @Suppress("FunctionName") - @OptIn(ExperimentalUnsignedTypes::class) - private fun readNextHeadButDoNotAssignTo_Head(): JceHead? { - if (input.endOfInput) { - return null - } - val var2 = input.readUByte() - val type = var2 and 15u - var tag = var2.toUInt() shr 4 - if (tag == 15u) { - tag = input.readUByte().toUInt() - } - return JceHead( - tag = tag.toInt(), - type = type.toByte() - ) - } - - /** - * 使用这个 [JceHead]. - * [block] 结束后将会 [准备下一个 [JceHead]][prepareNextHead] - */ - inline fun useHead(crossinline block: (JceHead) -> R): R { - return currentHead.let(block).also { prepareNextHead() } - } - - /** - * 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则返回 `null` - */ - inline fun skipToHeadAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? { - return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() } - } - - /** - * 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常 - */ - inline fun skipToHeadAndUseIfPossibleOrFail( - tag: Int, - crossinline message: () -> String = { "tag not found: $tag" }, - crossinline block: (JceHead) -> R - ): R { - return checkNotNull(skipToHeadAndUseIfPossibleOrNull(tag, block), message) - } - - tailrec fun skipToHeadOrNull(tag: Int): JceHead? { - val current: JceHead = currentHeadOrNull ?: return null // no backing field - - return when { - current.tag > tag -> null // tag 大了,即找不到 - current.tag == tag -> current // 满足需要. - else -> { // tag 小了 - skipField(current.type) - check(prepareNextHead()) { "cannot skip to tag $tag, early EOF" } - skipToHeadOrNull(tag) - } - } - } - - inline fun skipToHeadOrFail( - tag: Int, - message: () -> String = { "head not found: $tag" } - ): JceHead { - return checkNotNull(skipToHeadOrNull(tag), message) - } - - @OptIn(ExperimentalUnsignedTypes::class) - @PublishedApi - internal fun skipField(type: Byte): Unit { - JceDecoder.println { - "skipping ${JceHead.findJceTypeName( - type - )}" - } - when (type) { - Jce.BYTE -> this.input.discardExact(1) - Jce.SHORT -> this.input.discardExact(2) - Jce.INT -> this.input.discardExact(4) - Jce.LONG -> this.input.discardExact(8) - Jce.FLOAT -> this.input.discardExact(4) - Jce.DOUBLE -> this.input.discardExact(8) - Jce.STRING1 -> this.input.discardExact(this.input.readUByte().toInt()) - Jce.STRING4 -> this.input.discardExact(this.input.readInt()) - Jce.MAP -> { // map - JceDecoder.structureHierarchy++ - var count: Int = 0 - nextHead() // avoid shadowing, don't remove - repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping map" }) { - readJceIntValue(it).also { count = it * 2 } - } * 2) { - skipField(currentHead.type) - if (it != count - 1) { // don't read last head - nextHead() - } - } - JceDecoder.structureHierarchy-- - } - Jce.LIST -> { // list - JceDecoder.structureHierarchy++ - var count: Int = 0 - nextHead() // avoid shadowing, don't remove - repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping list" }) { head -> - readJceIntValue(head).also { count = it } - }) { - skipField(currentHead.type) - if (it != count - 1) { // don't read last head - nextHead() - } - } - JceDecoder.structureHierarchy-- - } - Jce.STRUCT_BEGIN -> { - JceDecoder.structureHierarchy++ - var head: JceHead - do { - head = nextHead() - skipField(head.type) - } while (head.type != Jce.STRUCT_END) - JceDecoder.structureHierarchy-- - } - Jce.STRUCT_END, Jce.ZERO_TYPE -> { - } - Jce.SIMPLE_LIST -> { - JceDecoder.structureHierarchy++ - var head = nextHead() - check(head.type == Jce.BYTE) { "bad simple list element type: " + head.type } - check(head.tag == 0) { "simple list element tag must be 0, but was ${head.tag}" } - - head = nextHead() - check(head.tag == 0) { "tag for size for simple list must be 0, but was ${head.tag}" } - this.input.discardExact(readJceIntValue(head)) - JceDecoder.structureHierarchy-- - } - else -> error("invalid type: $type") - } - } - - // region readers - fun readJceIntValue(head: JceHead): Int { - //println("readJceIntValue: $head") - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte().toInt() - Jce.SHORT -> input.readShort().toInt() - Jce.INT -> input.readInt() - else -> error("type mismatch: $head, remaining=${input.readBytes().toUHexString()}") - } - } - - fun readJceShortValue(head: JceHead): Short { - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte().toShort() - Jce.SHORT -> input.readShort() - else -> error("type mismatch: $head") - } - } - - fun readJceLongValue(head: JceHead): Long { - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte().toLong() - Jce.SHORT -> input.readShort().toLong() - Jce.INT -> input.readInt().toLong() - Jce.LONG -> input.readLong() - else -> error("type mismatch ${head.type}") - } - } - - fun readJceByteValue(head: JceHead): Byte { - //println("readJceByteValue: $head") - return when (head.type) { - Jce.ZERO_TYPE -> 0 - Jce.BYTE -> input.readByte() - else -> error("type mismatch: $head") - } - } - - fun readJceFloatValue(head: JceHead): Float { - return when (head.type) { - Jce.ZERO_TYPE -> 0f - Jce.FLOAT -> input.readFloat() - else -> error("type mismatch: $head") - } - } - - @OptIn(ExperimentalUnsignedTypes::class) - fun readJceStringValue(head: JceHead): String { - //println("readJceStringValue: $head") - return when (head.type) { - Jce.STRING1 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset) - Jce.STRING4 -> input.readString( - input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, - charset = charset.kotlinCharset - ) - else -> error("type mismatch: $head, expecting 6 or 7 (for string)") - } - } - - fun readJceDoubleValue(head: JceHead): Double { - return when (head.type.toInt()) { - 12 -> 0.0 - 4 -> input.readFloat().toDouble() - 5 -> input.readDouble() - else -> error("type mismatch: $head") - } - } - - fun readJceBooleanValue(head: JceHead): Boolean { - return readJceByteValue(head) == 1.toByte() - } -} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceNew.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceNew.kt deleted file mode 100644 index 994a8c304..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/JceNew.kt +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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.qqandroid.utils.io.serialization.jce - -import kotlinx.io.core.* -import kotlinx.serialization.BinaryFormat -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.SerialFormat -import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.modules.EmptyModule -import kotlinx.serialization.modules.SerialModule -import net.mamoe.mirai.qqandroid.utils.io.serialization.IOFormat -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceOld -import net.mamoe.mirai.qqandroid.utils.toReadPacket - -/** - * Jce 数据结构序列化和反序列化器. - * - * @author Him188 - */ -internal class Jce( - override val context: SerialModule, - val charset: JceCharset -) : SerialFormat, IOFormat, BinaryFormat { - override fun dumpTo(serializer: SerializationStrategy, ojb: T, output: Output) { - output.writePacket(JceOld.byCharSet(this.charset).dumpAsPacket(serializer, ojb)) - } - - override fun load(deserializer: DeserializationStrategy, input: Input): T { - return JceDecoder( - JceInput( - input, - charset - ), context - ).decodeSerializableValue(deserializer) - } - - override fun dump(serializer: SerializationStrategy, value: T): ByteArray { - return buildPacket { dumpTo(serializer, value, this) }.readBytes() - } - - override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T { - return load(deserializer, bytes.toReadPacket()) - } - - companion object { - val UTF_8 = Jce( - EmptyModule, - JceCharset.UTF8 - ) - val GBK = Jce( - EmptyModule, - JceCharset.GBK - ) - - fun byCharSet(c: JceCharset): Jce { - return if (c == JceCharset.UTF8) UTF_8 else GBK - } - - internal const val BYTE: Byte = 0 - internal const val DOUBLE: Byte = 5 - internal const val FLOAT: Byte = 4 - internal const val INT: Byte = 2 - internal const val JCE_MAX_STRING_LENGTH = 104857600 - internal const val LIST: Byte = 9 - internal const val LONG: Byte = 3 - internal const val MAP: Byte = 8 - internal const val SHORT: Byte = 1 - internal const val SIMPLE_LIST: Byte = 13 - internal const val STRING1: Byte = 6 - internal const val STRING4: Byte = 7 - internal const val STRUCT_BEGIN: Byte = 10 - internal const val STRUCT_END: Byte = 11 - internal const val ZERO_TYPE: Byte = 12 - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/common.kt deleted file mode 100644 index 86bfee2bd..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/jce/common.kt +++ /dev/null @@ -1,118 +0,0 @@ -/* - * 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.qqandroid.utils.io.serialization.jce - -import kotlinx.io.core.Output -import kotlinx.serialization.SerialDescriptor -import kotlinx.serialization.SerialInfo - - -/** - * 标注 JCE 序列化时使用的 ID - */ -@SerialInfo -@Target(AnnotationTarget.PROPERTY) -internal annotation class JceId(val id: Int) - -/** - * 类中元素的 tag - * - * 保留这个结构, 为将来增加功能的兼容性. - */ -@PublishedApi -internal abstract class JceTag { - abstract val id: Int - - internal var isSimpleByteArray: Boolean = false -} - -internal object JceTagListElement : JceTag() { - override val id: Int get() = 0 - override fun toString(): String { - return "JceTagListElement" - } -} - -internal object JceTagMapEntryKey : JceTag() { - override val id: Int get() = 0 - override fun toString(): String { - return "JceTagMapEntryKey" - } -} - -internal object JceTagMapEntryValue : JceTag() { - override val id: Int get() = 1 - override fun toString(): String { - return "JceTagMapEntryValue" - } -} - -internal data class JceTagCommon( - override val id: Int -) : JceTag() - -internal fun JceHead.checkType(type: Byte, message: String, tag: JceTag, descriptor: SerialDescriptor) { - check(this.type == type) { - "type mismatch. " + - "Expected ${JceHead.findJceTypeName(type)}, " + - "actual ${JceHead.findJceTypeName(this.type)} for $message. " + - "Tag info: " + - "id=${tag.id}, " + - "name=${descriptor.getElementName(tag.id)} " + - "in ${descriptor.serialName}" } -} - -@PublishedApi -internal fun Output.writeJceHead(type: Byte, tag: Int) { - if (tag < 15) { - writeByte(((tag shl 4) or type.toInt()).toByte()) - return - } - if (tag < 256) { - writeByte((type.toInt() or 0xF0).toByte()) - writeByte(tag.toByte()) - return - } - error("tag is too large: $tag") -} - -@OptIn(ExperimentalUnsignedTypes::class) -inline class JceHead(private val value: Long) { - constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong()) - - val tag: Int get() = (value ushr 32).toInt() - val type: Byte get() = value.toUInt().toByte() - - override fun toString(): String { - return "JceHead(tag=$tag, type=$type(${findJceTypeName(type)}))" - } - - companion object { - fun findJceTypeName(type: Byte): String { - return when (type) { - Jce.BYTE -> "Byte" - Jce.DOUBLE -> "Double" - Jce.FLOAT -> "Float" - Jce.INT -> "Int" - Jce.LIST -> "List" - Jce.LONG -> "Long" - Jce.MAP -> "Map" - Jce.SHORT -> "Short" - Jce.SIMPLE_LIST -> "SimpleList" - Jce.STRING1 -> "String1" - Jce.STRING4 -> "String4" - Jce.STRUCT_BEGIN -> "StructBegin" - Jce.STRUCT_END -> "StructEnd" - Jce.ZERO_TYPE -> "Zero" - else -> error("illegal jce type: $type") - } - } - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt index ad04fd662..5ea7bf77c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/serialization/utils.kt @@ -16,15 +16,14 @@ import kotlinx.io.core.* import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialDescriptor import kotlinx.serialization.SerializationStrategy +import moe.him188.jcekt.Jce import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf import net.mamoe.mirai.qqandroid.utils.io.readPacketExact -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce import net.mamoe.mirai.qqandroid.utils.read -import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -34,25 +33,21 @@ internal fun ByteArray.loadWithUniPacket( ): T = this.read { readUniPacket(deserializer, name) } internal fun ByteArray.loadAs( - deserializer: DeserializationStrategy, - c: JceCharset = JceCharset.UTF8 -): T = this.read { Jce.byCharSet(c).load(deserializer, this) } + deserializer: DeserializationStrategy +): T = this.read { Jce.UTF_8.load(deserializer, this) } internal fun BytePacketBuilder.writeJceStruct( serializer: SerializationStrategy, - struct: T, - charset: JceCharset = JceCharset.UTF8 + struct: T ) { - Jce.byCharSet(charset).dumpTo(serializer, struct, this) + Jce.UTF_8.dumpTo(serializer, struct, this) } internal fun ByteReadPacket.readJceStruct( serializer: DeserializationStrategy, - charset: JceCharset = JceCharset.UTF8, length: Int = this.remaining.toInt() ): T { - @OptIn(MiraiInternalAPI::class) - return Jce.byCharSet(charset).load(serializer, this.readPacketExact(length)) + return Jce.UTF_8.load(serializer, this.readPacketExact(length)) } /** @@ -103,9 +98,8 @@ private fun ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String } internal fun T.toByteArray( - serializer: SerializationStrategy, - c: JceCharset = JceCharset.UTF8 -): ByteArray = Jce.byCharSet(c).dump(serializer, this) + serializer: SerializationStrategy +): ByteArray = Jce.UTF_8.dump(serializer, this) internal fun BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy, v: T) { this.writeFully(v.toByteArray(serializer)) @@ -140,19 +134,12 @@ internal fun jceRequestSBuffer( name: String, serializer: SerializationStrategy, jceStruct: T -): ByteArray = jceRequestSBuffer(name, serializer, jceStruct, JceCharset.UTF8) - -internal fun jceRequestSBuffer( - name: String, - serializer: SerializationStrategy, - jceStruct: T, - charset: JceCharset ): ByteArray { return RequestDataVersion3( mapOf( name to JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0 ) - ).toByteArray(RequestDataVersion3.serializer(), charset) + ).toByteArray(RequestDataVersion3.serializer()) } private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A) diff --git a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/io/serialization/JceInputTest.kt b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/io/serialization/JceInputTest.kt deleted file mode 100644 index 78671f853..000000000 --- a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid/io/serialization/JceInputTest.kt +++ /dev/null @@ -1,651 +0,0 @@ -@file:Suppress("unused", "DEPRECATION_ERROR") - -package net.mamoe.mirai.qqandroid.io.serialization - -import kotlinx.io.core.buildPacket -import kotlinx.io.core.toByteArray -import kotlinx.io.core.writeFully -import kotlinx.serialization.MissingFieldException -import kotlinx.serialization.Serializable -import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FileStoragePushFSSvcListFuckKotlin -import net.mamoe.mirai.qqandroid.utils.autoHexToBytes -import net.mamoe.mirai.qqandroid.utils.io.JceStruct -import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset -import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.* -import net.mamoe.mirai.qqandroid.utils.io.serialization.loadAs -import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - - -internal const val BYTE: Byte = 0 -internal const val DOUBLE: Byte = 5 -internal const val FLOAT: Byte = 4 -internal const val INT: Byte = 2 -internal const val JCE_MAX_STRING_LENGTH = 104857600 -internal const val LIST: Byte = 9 -internal const val LONG: Byte = 3 -internal const val MAP: Byte = 8 -internal const val SHORT: Byte = 1 -internal const val SIMPLE_LIST: Byte = 13 -internal const val STRING1: Byte = 6 -internal const val STRING4: Byte = 7 -internal const val STRUCT_BEGIN: Byte = 10 -internal const val STRUCT_END: Byte = 11 -internal const val ZERO_TYPE: Byte = 12 - - -@Suppress("INVISIBLE_MEMBER") // bug -internal class JceInputTest { - init { - JceDecoder.debuggingMode = true - } - - - @Test - fun testConfigPush() { - val data = """ - 9A - 09 00 0B - 0A - 00 0F - 19 00 01 - 0A - 12 71 19 A3 B4 - 20 50 - 0B - 29 0C - 0B - 0A - 00 04 - 19 00 01 - 0A - 12 0B 27 59 65 - 20 50 - 0B - 29 0C - 0B - 0A - 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B - AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 - - """.trimIndent().autoHexToBytes() - /* - 9A - 09 00 0B - 0A - 00 0F - 19 00 01 - 0A - 12 71 19 A3 B4 - 20 50 - 0B - 29 0C - 0B - 0A - 00 04 - 19 00 01 - 0A - 12 0B 27 59 65 - 20 50 - 0B - 29 0C - 0B - 0A - 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B - AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 - - */ - /* - - 39 00 06 - 0A - 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 31 - 20 50 - 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 34 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 34 35 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 32 20 50 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 2E 32 35 33 20 50 0B 0A 16 11 73 63 61 6E 6E 6F 6E 2E 33 67 2E 71 71 2E 63 6F 6D 20 50 0B 49 00 04 0A 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 5A 09 00 03 0A 00 01 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 05 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 0C 3C 0B 0A 00 0A 19 00 04 0A 00 01 16 0E 31 31 33 2E 39 36 2E 32 33 32 2E 31 30 38 20 50 0B 0A 00 01 16 0D 31 38 33 2E 33 2E 32 33 33 2E 32 32 35 21 1F 90 0B 0A 00 01 16 0E 31 32 33 2E 31 35 30 2E 37 36 2E 31 36 38 21 01 BB 0B 0A 00 01 16 0D 31 38 30 2E 31 36 33 2E 32 35 2E 33 38 20 50 0B 29 00 05 0A 0C 11 20 00 20 10 30 01 0B 0A 00 01 11 20 00 20 08 30 02 0B 0A 00 02 11 20 00 20 08 30 01 0B 0A 00 03 12 00 01 00 00 20 08 30 02 0B 0A 00 04 11 20 00 20 08 30 02 0B 3C 0B 1D 00 00 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 2D 00 00 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 32 76 E4 B8 DD 40 01 5D 00 01 02 54 8A 50 D0 04 0A 68 CA 62 F1 01 C2 AF E6 CF 29 4B 18 71 B5 EE 6B 63 EB F0 0B AB EE A0 5C 20 B9 83 E2 52 F7 BF C7 46 80 BC C3 7F 22 6B 6E 23 42 D0 8F C8 6A C4 F4 49 AA E7 94 EF D4 80 0A E4 8B BF E2 C0 4F FC C5 3F 97 1A E8 0F 0F 7D 06 47 62 C3 C8 07 4F E6 F6 E9 DB CB 4C F5 95 6A AD EC FD D0 46 A5 16 8D 30 02 D5 8A 86 2E 5F E8 D6 8C 12 10 33 6E 59 70 73 47 38 52 6E 48 6A 64 51 48 46 54 1A 40 08 01 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 40 08 05 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 1A 78 08 0A 12 0D 08 01 15 71 60 E8 6C 18 50 20 01 28 01 12 0E 08 01 15 B7 03 E9 E1 18 90 3F 20 01 28 01 12 0E 08 01 15 7B 96 4C A8 18 BB 03 20 02 28 00 12 0D 08 01 15 B4 A3 19 26 18 50 20 04 28 00 22 09 08 00 10 80 40 18 10 20 01 22 09 08 01 10 80 40 18 08 20 02 22 09 08 02 10 80 40 18 08 20 01 22 0A 08 03 10 80 80 04 18 08 20 02 22 09 08 04 10 80 40 18 08 20 02 20 01 32 04 08 00 10 01 3A 2A 08 10 10 10 18 09 20 09 28 0F 30 0F 38 05 40 05 48 5A 50 01 58 5A 60 5A 68 5A 70 5A 78 0A 80 01 0A 88 01 0A 90 01 0A 98 01 0A 42 0A 08 00 10 00 18 00 20 00 28 00 4A 06 08 01 10 01 18 03 52 42 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 20 00 5A 40 08 01 12 0A 08 00 10 80 80 04 18 10 20 02 12 0A 08 01 10 80 80 04 18 08 20 02 12 0A 08 02 10 80 80 01 18 08 20 01 12 0A 08 03 10 80 80 04 18 08 20 02 12 0A 08 04 10 80 80 04 18 08 20 02 18 00 70 02 78 02 80 01 FA 01 0B 69 00 01 0A 16 26 69 6D 67 63 61 63 68 65 2E 71 71 2E 63 6F 6D 2E 73 63 68 65 64 2E 70 31 76 36 2E 74 64 6E 73 76 36 2E 63 6F 6D 2E 20 50 0B 79 00 02 0A 16 0E 31 30 31 2E 32 32 37 2E 31 33 31 2E 36 37 20 50 0B 0A 16 0D 36 31 2E 31 35 31 2E 31 38 33 2E 32 31 20 50 0B 8A 06 0F 31 37 31 2E 31 31 32 2E 32 32 36 2E 32 33 37 10 03 0B 9A 09 00 0B 0A 00 0F 19 00 01 0A 12 71 19 A3 B4 20 50 0B 29 0C 0B 0A 00 04 19 00 01 0A 12 0B 27 59 65 20 50 0B 29 0C 0B 0A 00 0D 19 00 02 0A 12 55 31 BA DE 20 50 0B 0A 12 5B A0 6A 72 20 50 0B 29 0C 0B 0A 00 03 19 00 02 0A 12 C3 B9 D3 74 20 50 0B 0A 12 CC 43 E4 DD 20 50 0B 29 0C 0B 0A 00 07 19 00 01 0A 12 75 A2 E3 65 20 50 0B 29 0C 0B 0A 00 09 19 00 02 0A 12 BC 6C 24 B7 20 50 0B 0A 12 A6 6C 24 B7 20 50 0B 29 0C 0B 0A 00 0A 19 00 02 0A 12 11 B4 12 0E 20 50 0B 0A 12 15 8C D7 0E 20 50 0B 29 0C 0B 0A 00 05 19 00 01 0A 12 1D E2 03 B7 20 50 0B 29 0C 0B 0A 00 08 19 00 02 0A 12 DE 3F 5B 65 20 50 0B 0A 12 78 09 61 B4 20 50 0B 29 0C 0B 0A 00 06 19 00 02 0A 12 16 CF 97 3D 20 50 0B 0A 12 54 10 59 65 20 50 0B 29 0C 0B 0A 00 0E 19 00 02 0A 12 76 01 B1 6F 20 50 0B 0A 12 6B 89 31 3A 20 50 0B 29 0C 0B 0B AD 00 01 01 5B 08 01 10 A4 F6 AA 16 18 00 22 0A 31 39 39 34 37 30 31 30 32 31 28 AB E1 89 EF 0E 32 12 08 8E A4 D8 A5 09 10 50 18 89 D8 AC F0 08 20 50 28 64 32 12 08 8E A4 C4 DD 08 10 50 18 89 F4 DE E0 05 20 50 28 64 32 13 08 B4 C7 DA B0 02 10 50 18 8A EE D4 F2 0D 20 50 28 C8 01 32 13 08 B4 C7 DA A0 02 10 50 18 8A EC D0 86 0E 20 50 28 C8 01 32 13 08 8C 9D 9B 85 07 10 50 18 89 D6 AD 9C 09 20 50 28 AC 02 32 13 08 B7 81 97 F6 06 10 50 18 8A EC D4 96 02 20 50 28 AC 02 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 29 10 50 18 89 EC 8C B1 05 20 50 28 64 3A 1E 0A 10 24 0E 00 E1 A9 00 00 50 00 00 00 00 00 00 00 64 10 50 18 89 EC 8C D1 07 20 50 28 64 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 6F 10 50 18 E4 E6 B1 F0 04 20 50 28 C8 01 3A 1F 0A 10 24 0E 00 FF F1 01 00 10 00 00 00 00 00 00 01 72 10 50 18 E4 E6 AD F0 0E 20 50 28 C8 01 3A 1F 0A 10 24 09 8C 1E 75 B0 00 13 00 00 00 00 00 00 00 36 10 50 18 89 EC 9C E8 0D 20 50 28 AC 02 3A 1F 0A 10 24 09 8C 54 10 03 00 10 00 00 00 00 00 00 00 55 10 50 18 89 CA 8C A0 01 20 50 28 AC 02 - - */ - - data.loadAs(FileStoragePushFSSvcListFuckKotlin.serializer()) - } - - @Test - fun testIntToStructMap() { - @kotlinx.serialization.Serializable - data class VipOpenInfo( - @JceId(0) val open: Boolean? = false, - @JceId(1) val iVipType: Int = -1, - @JceId(2) val iVipLevel: Int = -1, - @JceId(3) val iVipFlag: Int? = null, - @JceId(4) val nameplateId: Long? = null - ) : JceStruct - - @Serializable - data class VipBaseInfo( - @JceId(0) val mOpenInfo: Map? = null - ) : JceStruct - - - @Serializable - data class FriendInfo( - @JceId(0) val friendUin: Long, - @JceId(14) val nick: String = "", - @JceId(19) val oVipInfo: VipBaseInfo? = null, //? bad - @JceId(20) val network: Byte? = null - ) : JceStruct - - val value = FriendInfo( - friendUin = 123, - nick = "h", - oVipInfo = VipBaseInfo( - mapOf( - 1 to VipOpenInfo(true, -1), - 999999999 to VipOpenInfo(true, -1) - ) - ), - network = 1 - ) - assertEquals( - value.toString(), - Jce.UTF_8.load(FriendInfo.serializer(), value.toByteArray(FriendInfo.serializer())).toString() - ) - } - - @Test - fun testSkippingMap() { - @Serializable - data class TestSerializableClassC( - @JceId(5) val value3: Int = 123123 - ) - - @Serializable - data class TestSerializableClassB( - @JceId(0) val value: Int, - @JceId(123) val nested2: TestSerializableClassC, - @JceId(5) val value5: Int - ) - - @Serializable - data class TestSerializableClassA( - //@JceId(0) val map: Map - @JceId(1) val optional: String = "" - ) - - - val input = buildPacket { - writeJceHead(MAP, 0) // TestSerializableClassB - writeJceHead(BYTE, 0) - writeByte(1); - - { - writeJceHead(STRUCT_BEGIN, 0); - { - writeJceHead(INT, 0) - writeInt(123) - - writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 5) - writeInt(9) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(STRUCT_BEGIN, 1); - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - }() - - writeJceHead(STRING1, 1) - writeByte(1) - writeStringUtf8("1") - } - - assertEquals( - TestSerializableClassA( - /*mapOf( - TestSerializableClassB(123, TestSerializableClassC(123123), 9) - to TestSerializableClassC(123123) - ),*/ - - "1" - ), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testFuckingComprehensiveStruct() { - @Serializable - data class TestSerializableClassC( - @JceId(5) val value3: Int = 123123 - ) - - @Serializable - data class TestSerializableClassB( - @JceId(0) val value: Int, - @JceId(123) val nested2: TestSerializableClassC, - @JceId(5) val value5: Int - ) - - @Serializable - data class TestSerializableClassA( - @JceId(0) val map: Map - ) - - - val input = buildPacket { - writeJceHead(MAP, 0) // TestSerializableClassB - writeJceHead(BYTE, 0) - writeByte(1) - - writeJceHead(STRUCT_BEGIN, 0); - { - writeJceHead(INT, 0) - writeInt(123) - - writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 5) - writeInt(9) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(STRUCT_BEGIN, 1); - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - } - - assertEquals( - TestSerializableClassA( - mapOf( - TestSerializableClassB(123, TestSerializableClassC(123123), 9) - to TestSerializableClassC(123123) - ) - ), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testNestedJceStruct() { - @Serializable - data class TestSerializableClassC( - @JceId(5) val value3: Int - ) - - @Serializable - data class TestSerializableClassB( - @JceId(0) val value: Int, - @JceId(123) val nested2: TestSerializableClassC, - @JceId(5) val value5: Int - ) - - @Serializable - data class TestSerializableClassA( - @JceId(0) val value1: Int, - @JceId(4) val notOptional: Int, - @JceId(1) val nestedStruct: TestSerializableClassB, - @JceId(2) val optional: Int = 3 - ) - - val input = buildPacket { - writeJceHead(INT, 0) - writeInt(444) - - writeJceHead(STRUCT_BEGIN, 1); // TestSerializableClassB - { - writeJceHead(INT, 0) - writeInt(123) - - writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC - { - writeJceHead(INT, 5) - writeInt(123123) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 5) - writeInt(9) - }() - writeJceHead(STRUCT_END, 0) - - writeJceHead(INT, 4) - writeInt(5) - } - - assertEquals( - TestSerializableClassA( - 444, - 5, - TestSerializableClassB(123, TestSerializableClassC(123123), 9) - ), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testNestedList() { - @Serializable - data class TestSerializableClassA( - // @JceId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3), - @JceId(3) val byteArray2: List> = listOf(listOf(1, 2, 3, 4), listOf(1, 2, 3, 4)) - ) - - val input = buildPacket { - //writeJceHead(SIMPLE_LIST, 0) - //writeJceHead(BYTE, 0) - - //writeJceHead(BYTE, 0) - //byteArrayOf(1, 2, 3).let { - // writeByte(it.size.toByte()) - // writeFully(it) - //} - - writeJceHead(LIST, 3) - - writeJceHead(BYTE, 0) - writeByte(2) - listOf(listOf(1, 2, 3, 4), listOf(1, 2, 3, 4)).forEach { child -> - writeJceHead(LIST, 0) - - writeJceHead(BYTE, 0) - writeByte(child.size.toByte()) - - child.forEach { - writeJceHead(INT, 0) - writeInt(it) - } - } - } - - assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) - } - - @Test - fun testMap() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byteArray: Map - ) - - val input = buildPacket { - writeJceHead(MAP, 0) - - mapOf(1 to 2, 33 to 44).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - - it.forEach { (key, value) -> - writeJceHead(INT, 0) - writeInt(key) - - writeJceHead(INT, 1) - writeInt(value) - } - } - - writeJceHead(SIMPLE_LIST, 3) - writeJceHead(BYTE, 0) - - byteArrayOf(1, 2, 3, 4).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - writeFully(it) - } - } - - assertEquals( - TestSerializableClassA(mapOf(1 to 2, 33 to 44)), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testMapStringInt() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byteArray: Map - ) - - val input = buildPacket { - writeJceHead(MAP, 0) - - mapOf("str1" to 2, "str2" to 44).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - - it.forEach { (key, value) -> - writeJceHead(STRING1, 0) - writeByte(key.length.toByte()) - writeStringUtf8(key) - - writeJceHead(INT, 1) - writeInt(value) - } - } - } - - assertEquals( - TestSerializableClassA(mapOf("str1" to 2, "str2" to 44)), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input) - ) - } - - @Test - fun testMapStringByteArray() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val map: Map - ) { - override fun toString(): String { - @Suppress("EXPERIMENTAL_API_USAGE") - return map.entries.joinToString { "${it.key}=${it.value.contentToString()}" } - } - } - - val input = buildPacket { - writeJceHead(MAP, 0) - - mapOf("str1" to byteArrayOf(2, 3, 4), "str2" to byteArrayOf(2, 3, 4)).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - - it.forEach { (key, value) -> - writeJceHead(STRING1, 0) - writeByte(key.length.toByte()) - writeFully(key.toByteArray()) - - writeJceHead(SIMPLE_LIST, 1) - writeJceHead(BYTE, 0) - writeJceHead(INT, 0) - writeInt(value.size) - writeFully(value) - } - } - } - - assertEquals( - TestSerializableClassA(mapOf("str1" to byteArrayOf(2, 3, 4), "str2" to byteArrayOf(2, 3, 4))).toString(), - Jce.UTF_8.load(TestSerializableClassA.serializer(), input).toString() - ) - } - - @Test - fun testSimpleByteArray() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3), - @JceId(3) val byteArray2: List = listOf(1, 2, 3, 4) - ) { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || this::class != other::class) return false - - other as TestSerializableClassA - - if (!byteArray.contentEquals(other.byteArray)) return false - if (byteArray2 != other.byteArray2) return false - - return true - } - - override fun hashCode(): Int { - var result = byteArray.contentHashCode() - result = 31 * result + byteArray2.hashCode() - return result - } - } - - val input = buildPacket { - writeJceHead(SIMPLE_LIST, 0) - writeJceHead(BYTE, 0) - - byteArrayOf(1, 2, 3).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - writeFully(it) - } - - writeJceHead(SIMPLE_LIST, 3) - writeJceHead(BYTE, 0) - - byteArrayOf(1, 2, 3, 4).let { - writeJceHead(BYTE, 0) - writeByte(it.size.toByte()) - writeFully(it) - } - } - - assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) - } - - - @Test - fun testSerializableClassA() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byte: Byte = 66, - @JceId(1) val short: Short = 123, - @JceId(3) val int: Int = 123456, - @JceId(8) val float: Float = 123f, - @JceId(15) val long: Long = 123456789123456789L, - @JceId(16) val double: Double = 123456.0, - @JceId(17) val boolean: Boolean = true, - @JceId(11111) val nullable: Int? = null, - @JceId(111112) val nullable2: Int? = null, - @JceId(111113) val optional: Int = 123 - ) - - val input = buildPacket { - writeJceHead(BYTE, 0) - writeByte(66) - writeJceHead(SHORT, 1) - writeShort(123) - writeJceHead(INT, 3) - writeInt(123456) - writeJceHead(FLOAT, 8) - writeFloat(123f) - writeJceHead(LONG, 15) - writeLong(123456789123456789L) - writeJceHead(DOUBLE, 16) - writeDouble(123456.0) - writeJceHead(BYTE, 17) - writeByte(1) // boolean - } - - assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) - } - - @Test - fun testNoSuchField() { - @Serializable - data class TestSerializableClassA( - @JceId(0) val byte: Byte = 66, - @JceId(1) val short: Short = 123, - @JceId(3) val int: Int - ) - - val input = buildPacket { - writeJceHead(BYTE, 0) - writeByte(66) - writeJceHead(SHORT, 1) - writeShort(123) - } - - assertFailsWith { Jce.UTF_8.load(TestSerializableClassA.serializer(), input) } - } - - @Test - fun testHeadSkip() { - val input = JceInput(buildPacket { - writeJceHead(BYTE, 0) - writeByte(66) - writeJceHead(SHORT, 1) - writeShort(123) - writeJceHead(INT, 3) - writeInt(123456) - writeJceHead(FLOAT, 8) - writeFloat(123f) - writeJceHead(LONG, 15) - writeLong(123456789123456789L) - writeJceHead(DOUBLE, 16) - writeDouble(123456.0) - writeJceHead(BYTE, 17) - writeByte(1) // boolean - }, JceCharset.UTF8) - - assertEquals(123456, input.skipToHeadAndUseIfPossibleOrFail(3) { input.readJceIntValue(it) }) - - assertEquals(true, input.skipToHeadAndUseIfPossibleOrFail(17) { input.readJceBooleanValue(it) }) - - assertFailsWith { - input.skipToHeadAndUseIfPossibleOrFail(18) { - error("test failed") - } - } - } - - @Test - fun testReadPrimitive() { - val input = JceInput(buildPacket { - writeJceHead(BYTE, 0) - writeByte(66) - writeJceHead(SHORT, 1) - writeShort(123) - writeJceHead(INT, 3) - writeInt(123456) - writeJceHead(FLOAT, 8) - writeFloat(123f) - writeJceHead(LONG, 15) - writeLong(123456789123456789L) - writeJceHead(DOUBLE, 16) - writeDouble(123456.0) - writeJceHead(BYTE, 17) - writeByte(1) // boolean - }, JceCharset.UTF8) - assertEquals(66, input.useHead { input.readJceByteValue(it) }) - assertEquals(123, input.useHead { input.readJceShortValue(it) }) - assertEquals(123456, input.useHead { input.readJceIntValue(it) }) - assertEquals(123f, input.useHead { input.readJceFloatValue(it) }) - assertEquals(123456789123456789, input.useHead { input.readJceLongValue(it) }) - assertEquals(123456.0, input.useHead { input.readJceDoubleValue(it) }) - assertEquals(true, input.useHead { input.readJceBooleanValue(it) }) - } -} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt index 92014d246..1590f08b0 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt @@ -42,7 +42,6 @@ actual abstract class Group : Contact(), CoroutineScope { /** * 群设置 */ - @SinceMirai("0.30.0") actual abstract val settings: GroupSettings /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 993627ea7..260202d30 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -15,10 +15,8 @@ package net.mamoe.mirai import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.coroutines.io.ByteReadChannel import kotlinx.coroutines.launch import net.mamoe.mirai.contact.* -import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent @@ -29,7 +27,6 @@ import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic @@ -48,6 +45,8 @@ suspend inline fun B.alsoLogin(): B = also { login() } * * @see Contact 联系人 * @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close]) + * + * @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式. */ @Suppress("INAPPLICABLE_JVM_NAME") @OptIn(MiraiInternalAPI::class, LowLevelAPI::class) @@ -56,6 +55,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( /** * 复制一份此时的 [Bot] 实例列表. */ + @PlannedRemoval("1.2.0") @Deprecated("use botInstances instead", replaceWith = ReplaceWith("botInstances")) @JvmStatic val instances: List> @@ -64,7 +64,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( /** * 复制一份此时的 [Bot] 实例列表. */ - @SinceMirai("0.39.1") @JvmStatic val botInstances: List get() = BotImpl.instances.asSequence().mapNotNull { it.get() }.toList() @@ -72,7 +71,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( /** * 遍历每一个 [Bot] 实例 */ - inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) + fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) /** * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] @@ -89,20 +88,14 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( */ abstract val context: Context - @PlannedRemoval("1.0.0") - @Deprecated("use id instead", replaceWith = ReplaceWith("id")) - abstract val uin: Long - /** * QQ 号码. 实际类型为 uint */ - @SinceMirai("0.32.0") abstract override val id: Long /** * 昵称 */ - @SinceMirai("0.33.1") abstract val nick: String /** @@ -113,7 +106,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( // region contacts /** - * [QQ.id] 与 [Bot.uin] 相同的 [_lowLevelNewFriend] 实例 + * [User.id] 与 [Bot.id] 相同的 [_lowLevelNewFriend] 实例 */ abstract val selfQQ: Friend @@ -179,6 +172,8 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( /** * 获取图片下载链接 + * + * @see Image.queryUrl [Image] 的扩展函数 */ @JvmSynthetic abstract suspend fun queryImageUrl(image: Image): String @@ -193,7 +188,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * @param targetUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算 */ @MiraiExperimentalAPI - @SinceMirai("0.39.0") abstract fun constructMessageSource( kind: OfflineMessageSource.Kind, fromUin: Long, targetUin: Long, @@ -201,35 +195,12 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( originalMessage: MessageChain ): OfflineMessageSource - /** - * 获取图片下载链接并开始下载. - * - * @see ByteReadChannel.copyAndClose - * @see ByteReadChannel.copyTo - */ - @PlannedRemoval("1.0.0") - @Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING) - @MiraiExperimentalAPI - @JvmSynthetic - abstract suspend fun openChannel(image: Image): ByteReadChannel - - /** - * 添加一个好友 - * - * @param message 若需要验证请求时的验证消息. - * @param remark 好友备注 - */ - @JvmSynthetic - @MiraiExperimentalAPI("未支持") - abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult - /** * 通过好友验证 * * @param event 好友验证的事件对象 */ - @SinceMirai("0.35.0") @JvmSynthetic abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) @@ -239,7 +210,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * @param event 好友验证的事件对象 * @param blackList 拒绝后是否拉入黑名单 */ - @SinceMirai("0.35.0") @JvmSynthetic abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false) @@ -248,7 +218,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * * @param event 加群验证的事件对象 */ - @SinceMirai("0.35.0") @JvmSynthetic abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent) @@ -258,7 +227,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * @param event 加群验证的事件对象 * @param blackList 拒绝后是否拉入黑名单 */ - @SinceMirai("0.35.0") @JvmSynthetic abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false) @@ -268,7 +236,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * @param event 加群验证的事件对象 * @param blackList 忽略后是否拉入黑名单 */ - @SinceMirai("0.35.0") @JvmSynthetic abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false) @@ -277,7 +244,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * * @param event 邀请入群的事件对象 */ - @SinceMirai("0.39.4") @JvmSynthetic abstract suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) @@ -287,7 +253,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( * @param event 邀请入群的事件对象 */ @JvmSynthetic - @SinceMirai("0.39.4") abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) // endregion @@ -313,19 +278,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI( */ @MiraiInternalAPI abstract val network: BotNetworkHandler - - @PlannedRemoval("1.0.0.") - @get:JvmName("getSelfQQ") - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - val selfQQDeprecated: QQ - get() = selfQQ - - @PlannedRemoval("1.0.0.") - @JvmName("getFriend") - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun getFriendDeprecated(id: Long): QQ = this.getFriend(id) } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt index 917bef74a..057c24650 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt @@ -14,15 +14,16 @@ package net.mamoe.mirai import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.Context import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic /** - * 构造 [Bot] 的工厂. + * 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式. * - * 在协议模块中有各自的实现. - * - `mirai-core-timpc`: `TIMPC` - * - `mirai-core-qqandroid`: `QQAndroid` + * `mirai-core-qqandroid`: `QQAndroid` + * + * 在 JVM, 请查看 `BotFactoryJvm` */ -interface BotFactory { +expect interface BotFactory { /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ @@ -49,6 +50,7 @@ interface BotFactory { /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmSynthetic inline fun BotFactory.Bot( context: Context, qq: Long, @@ -59,6 +61,7 @@ inline fun BotFactory.Bot( /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmSynthetic inline fun BotFactory.Bot( context: Context, qq: Long, 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 56fe52d9f..e455dd646 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,8 @@ 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.ExperimentalTime +import kotlin.time.measureTime /* * 泛型 N 不需要向外(接口)暴露. @@ -46,10 +48,6 @@ abstract class BotImpl constructor( override val context: Context by context.unsafeWeakRef() - @Deprecated("use id instead", replaceWith = ReplaceWith("id")) - override val uin: Long - get() = this.id - final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) } init { @@ -60,7 +58,7 @@ abstract class BotImpl constructor( @PublishedApi internal val instances: LockFreeLinkedList> = LockFreeLinkedList() - inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach { + fun forEachInstance(block: (Bot) -> Unit) = instances.forEach { it.get()?.let(block) } @@ -90,6 +88,7 @@ abstract class BotImpl constructor( @Throws(LoginFailedException::class) // only protected abstract suspend fun relogin(cause: Throwable?) + @OptIn(ExperimentalTime::class) @Suppress("unused") private val offlineListener: Listener = this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event -> @@ -107,37 +106,39 @@ abstract class BotImpl constructor( } bot.logger.info { "Connection dropped by server or lost, retrying login" } - tailrec suspend fun reconnect() { - retryCatching(configuration.reconnectionRetryTimes, - except = LoginFailedException::class) { tryCount, _ -> - if (tryCount != 0) { - delay(configuration.reconnectPeriodMillis) - } - network.withConnectionLock { - /** - * [BotImpl.relogin] only, no [BotNetworkHandler.init] - */ - @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) - relogin((event as? BotOfflineEvent.Dropped)?.cause) - } - logger.info { "Reconnected successfully" } - BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() - return - }.getOrElse { - if (it is LoginFailedException && !it.killBot) { + val time = measureTime { + tailrec suspend fun reconnect() { + retryCatching(configuration.reconnectionRetryTimes, + except = LoginFailedException::class) { tryCount, _ -> + if (tryCount != 0) { + delay(configuration.reconnectPeriodMillis) + } + network.withConnectionLock { + /** + * [BotImpl.relogin] only, no [BotNetworkHandler.init] + */ + @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) + relogin((event as? BotOfflineEvent.Dropped)?.cause) + } + BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() + return + }.getOrElse { + if (it is LoginFailedException && !it.killBot) { + logger.info { "Cannot reconnect" } + logger.warning(it) + logger.info { "Retrying in 3s..." } + delay(3000) + return@getOrElse + } logger.info { "Cannot reconnect" } - logger.warning(it) - logger.info { "Retrying in 3s..." } - delay(3000) - return@getOrElse + throw it } - logger.info { "Cannot reconnect" } - throw it + reconnect() } reconnect() } - reconnect() + logger.info { "Reconnected successfully in ${time.inMilliseconds} ms" } } is BotOfflineEvent.Active -> { val msg = if (event.cause == null) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index ac0ea3dd1..cdfdebf3a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -32,11 +32,11 @@ import kotlin.jvm.JvmSynthetic /** - * 联系人. 虽然叫做联系人, 但他的子类有 [用户][User], 和 [群][Group]. + * 联系对象, 即可以与 [Bot] 互动的对象. 包含 [用户][User], 和 [群][Group]. */ abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot { /** - * 这个联系人所属 [Bot]. + * 这个联系对象所属 [Bot]. */ @WeakRefProperty abstract val bot: Bot @@ -44,10 +44,7 @@ abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot /** * 可以是 QQ 号码或者群号码. * - * 对于 [QQ], `uin` 与 `id` 是相同的意思. - * 对于 [Group], `groupCode` 与 `id` 是相同的意思. - * - * @see QQ.id + * @see User.id * @see Group.id */ abstract override val id: Long diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt index c82c75960..6560c8829 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt @@ -11,8 +11,9 @@ package net.mamoe.mirai.contact -import net.mamoe.mirai.utils.* -import kotlin.jvm.JvmName +import net.mamoe.mirai.utils.LockFreeLinkedList +import net.mamoe.mirai.utils.asSequence +import kotlin.jvm.JvmField /** @@ -20,9 +21,8 @@ import kotlin.jvm.JvmName * * @see ContactList.asSequence */ -@OptIn(MiraiInternalAPI::class) @Suppress("unused") -class ContactList(@MiraiInternalAPI("Implementation may change in future release") val delegate: LockFreeLinkedList) : +class ContactList internal constructor(@JvmField internal val delegate: LockFreeLinkedList) : Iterable { operator fun get(id: Long): C = delegate.asSequence().firstOrNull { it.id == id } ?: throw NoSuchElementException("Contact id $id") @@ -41,31 +41,6 @@ class ContactList(@MiraiInternalAPI("Implementation may change in f override fun iterator(): Iterator { return this.delegate.asSequence().iterator() } - - @PlannedRemoval("1.0.0") - @Suppress("PropertyName") - @get:JvmName("getIdContentString") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - val _idContentString: String - get() = this.idContentString - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - inline fun forEach(block: (C) -> Unit) = delegate.forEach(block) - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun first(): C { - forEach { return it } - throw NoSuchElementException() - } - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun firstOrNull(): C? { - forEach { return it } - return null - } } /** @@ -76,64 +51,17 @@ class ContactList(@MiraiInternalAPI("Implementation may change in f * ``` */ val ContactList<*>.idContentString: String - get() = "[" + @OptIn(MiraiInternalAPI::class) buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast( + get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast( 2 ) + "]" -@MiraiInternalAPI -operator fun LockFreeLinkedList.get(id: Long): C { +internal operator fun LockFreeLinkedList.get(id: Long): C { forEach { if (it.id == id) return it } throw NoSuchElementException("No such contact: $id") } -@MiraiInternalAPI -fun LockFreeLinkedList.getOrNull(id: Long): C? { +internal fun LockFreeLinkedList.getOrNull(id: Long): C? { forEach { if (it.id == id) return it } return null -} - -@OptIn(MiraiInternalAPI::class) -@PlannedRemoval("1.0.0") -@Deprecated( - "use firstOrNull from stdlib", - replaceWith = ReplaceWith("this.asSequence().firstOrNull(filter)"), - level = DeprecationLevel.ERROR -) -inline fun LockFreeLinkedList.firstOrNull(filter: (C) -> Boolean): C? { - forEach { if (filter(it)) return it } - return null -} - -@OptIn(MiraiInternalAPI::class) -@PlannedRemoval("1.0.0") -@Deprecated( - "use firstOrNull from stdlib", - replaceWith = ReplaceWith("firstOrNull(filter)"), - level = DeprecationLevel.ERROR -) -inline fun LockFreeLinkedList.filteringGetOrNull(filter: (C) -> Boolean): C? = - this.asSequence().firstOrNull(filter) - -@PlannedRemoval("1.0.0") -@Deprecated("use Iterator.toList from stdlib", level = DeprecationLevel.HIDDEN) -fun ContactList.toList(): List = toMutableList() - -@PlannedRemoval("1.0.0") -@Deprecated("use Iterator.toMutableList from stdlib", level = DeprecationLevel.HIDDEN) -@OptIn(MiraiInternalAPI::class) -fun ContactList.toMutableList(): MutableList = this.delegate.toMutableList() - -@PlannedRemoval("1.0.0") -@Deprecated("use Iterator.toSet from stdlib", level = DeprecationLevel.HIDDEN) -fun ContactList.toSet(): Set = toMutableSet() - -@PlannedRemoval("1.0.0") -@Deprecated("use Iterator.toMutableSet from stdlib", level = DeprecationLevel.HIDDEN) -@OptIn(MiraiInternalAPI::class) -fun ContactList.toMutableSet(): MutableSet = this.delegate.toMutableSet() - -@PlannedRemoval("1.0.0") -@Deprecated("use Iterator.asSequence from stdlib", level = DeprecationLevel.HIDDEN) -@OptIn(MiraiInternalAPI::class) -fun ContactList.asSequence(): Sequence = this.delegate.asSequence() \ No newline at end of file +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactOrBot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactOrBot.kt index cf6b570c2..dd76a4cd3 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactOrBot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactOrBot.kt @@ -10,7 +10,6 @@ package net.mamoe.mirai.contact import net.mamoe.mirai.Bot -import net.mamoe.mirai.utils.SinceMirai /** * 拥有 [id] 的对象. @@ -19,7 +18,6 @@ import net.mamoe.mirai.utils.SinceMirai * @see Contact * @see Bot */ -@SinceMirai("0.39.0") interface ContactOrBot { /** * QQ 号或群号. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/exceptions.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Exceptions.kt similarity index 93% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/exceptions.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Exceptions.kt index a83468687..3bc9e0a9f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/exceptions.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Exceptions.kt @@ -12,14 +12,12 @@ package net.mamoe.mirai.contact import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.utils.SinceMirai /** * 发送消息时消息过长抛出的异常. * * @see Contact.sendMessage */ -@SinceMirai("0.32.0") class MessageTooLargeException( val target: Contact, /** @@ -38,7 +36,6 @@ class MessageTooLargeException( * * @see Group.sendMessage */ -@SinceMirai("0.33.0") class BotIsBeingMutedException( val target: Group ) : RuntimeException("bot is being muted, remaining ${target.botMuteRemaining} seconds") diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt index 21ffab24e..4f1d4639f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Friend.kt @@ -16,21 +16,23 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent +import net.mamoe.mirai.message.FriendMessageEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.toMessage import kotlin.jvm.JvmSynthetic /** - * 好友 对象. - * 注意: 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot]. - * 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取. + * 代表一位好友. * - * 对于同一个 [Bot] 任何一个人的 [Friend] 实例都是单一的. - * 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取. + * 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot]. + * 对于同一个 [Bot], 任何一个人的 [Friend] 实例都是单一的. + * [Friend] 无法通过任何方式直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取. + * + * @see FriendMessageEvent */ @Suppress("DEPRECATION_ERROR") -abstract class Friend : QQ(), CoroutineScope { +abstract class Friend : User(), CoroutineScope { /** * 请求头像下载链接 */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index 4957d9dd2..9ba6ef3f8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -47,7 +47,6 @@ abstract class Group : Contact(), CoroutineScope { /** * 群设置 */ - @SinceMirai("0.30.0") abstract val settings: GroupSettings /** @@ -80,7 +79,6 @@ abstract class Group : Contact(), CoroutineScope { * 机器人在这个群里的权限 * * @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限 - * @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator] * * @see BotGroupPermissionChangeEvent 机器人群员修改 */ @@ -123,7 +121,6 @@ abstract class Group : Contact(), CoroutineScope { * @return 退出成功时 true; 已经退出时 false */ @JvmSynthetic - @SinceMirai("0.37.0") abstract suspend fun quit(): Boolean /** @@ -197,7 +194,6 @@ abstract class Group : Contact(), CoroutineScope { @Suppress("FunctionName") @JvmName("quit") @JavaFriendlyAPI - @SinceMirai("0.39.4") fun __quitBlockingForJava__(): Boolean = runBlocking { quit() } } @@ -206,7 +202,6 @@ abstract class Group : Contact(), CoroutineScope { * * @see Group.settings 获取群设置 */ -@SinceMirai("0.30.0") interface GroupSettings { /** * 入群公告, 没有时为空字符串. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/javaFriendly.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/JavaFriendly.common.kt similarity index 96% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/javaFriendly.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/JavaFriendly.common.kt index 49bfeaf45..d4dc378d8 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/javaFriendly.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/JavaFriendly.common.kt @@ -27,4 +27,4 @@ expect abstract class ContactJavaFriendlyAPI internal constructor() @Suppress("DEPRECATION_ERROR") @MiraiInternalAPI @JavaFriendlyAPI -expect abstract class MemberJavaFriendlyAPI internal constructor() : QQ // 将来会改为 User \ No newline at end of file +expect abstract class MemberJavaFriendlyAPI internal constructor() : User \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt index 3a86bad66..54fe722c0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -19,19 +19,19 @@ import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.toMessage import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.WeakRefProperty -import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic import kotlin.time.Duration import kotlin.time.ExperimentalTime /** - * 群成员. + * 代表一位群成员. * * 群成员可能也是好友, 但他们在对象类型上不同. * 群成员可以通过 [asFriend] 得到相关好友对象. + * + * ## 与好友相关的操作 + * [Member.isFriend] 判断此成员是否为好友 */ @Suppress("INAPPLICABLE_JVM_NAME") @OptIn(MiraiInternalAPI::class, JavaFriendlyAPI::class) @@ -95,12 +95,16 @@ abstract class Member : MemberJavaFriendlyAPI() { * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. * @return 机器人无权限时返回 `false` * + * @see Member.isMuted 判断此成员是否正处于禁言状态中 + * @see unmute 取消禁言此成员 + * * @see Int.minutesToSeconds * @see Int.hoursToSeconds * @see Int.daysToSeconds * * @see MemberMuteEvent 成员被禁言事件 - * @throws PermissionDeniedException 无权限修改时 + * + * @throws PermissionDeniedException 无权限修改时抛出 */ @JvmSynthetic abstract suspend fun mute(durationSeconds: Int) @@ -110,8 +114,11 @@ abstract class Member : MemberJavaFriendlyAPI() { * * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. * - * @see MemberUnmuteEvent 成员被取消禁言事件. - * @throws PermissionDeniedException 无权限修改时 + * @see Member.isMuted 判断此成员是否正处于禁言状态中 + * + * @see MemberUnmuteEvent 成员被取消禁言事件 + * + * @throws PermissionDeniedException 无权限修改时抛出 */ @JvmSynthetic abstract suspend fun unmute() @@ -164,26 +171,22 @@ abstract class Member : MemberJavaFriendlyAPI() { * * @throws IllegalStateException 当此成员不是好友时抛出 */ -@SinceMirai("0.39.0") fun Member.asFriend(): Friend = this.bot.getFriendOrNull(this.id) ?: error("$this is not a friend") /** - * 得到此成员作为好友的对象. + * 得到此成员作为好友的对象, 当此成员不是好友时返回 `null` */ -@SinceMirai("0.39.0") fun Member.asFriendOrNull(): Friend? = this.bot.getFriendOrNull(this.id) /** * 判断此成员是否为好友 */ -@SinceMirai("0.39.0") inline val Member.isFriend: Boolean get() = this.bot.friends.contains(this.id) /** * 如果此成员是好友, 则执行 [block] 并返回其返回值. 否则返回 `null` */ -@SinceMirai("0.39.0") inline fun Member.takeIfIsFriend(block: (Friend) -> R): R? { return this.asFriendOrNull()?.let(block) } @@ -191,7 +194,7 @@ inline fun Member.takeIfIsFriend(block: (Friend) -> R): R? { /** * 获取非空群名片或昵称. * - * 若 [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [QQ.nick] + * 若 [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [User.nick] */ val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick @@ -202,27 +205,15 @@ val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() * * 否则返回 [Member.nick] */ -@SinceMirai("0.39.0") val User.nameCardOrNick: String get() = when (this) { is Member -> this.nameCardOrNick else -> this.nick } -/** - * 判断改成员是否处于禁言状态. - */ -@JvmName("isMuted2") // make compiler happy -@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") -@kotlin.internal.InlineOnly // val Member.isMuted 编译在 JVM 也会产生 `public boolean isMuted(Member receive)` -@PlannedRemoval("1.0.0") -@Deprecated("use property instead", ReplaceWith("this.isMuted")) -inline fun Member.isMuted(): Boolean = this.isMuted - /** * 判断群成员是否处于禁言状态. */ -@SinceMirai("0.39.0") val Member.isMuted: Boolean get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/MemberPermission.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/MemberPermission.kt index 80b4d3527..dcc9cfbdf 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/MemberPermission.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/MemberPermission.kt @@ -7,17 +7,24 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("NOTHING_TO_INLINE") +@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.contact import net.mamoe.mirai.Bot -import net.mamoe.mirai.utils.SinceMirai +import kotlin.internal.InlineOnly /** * 群成员的权限. * * 可通过 [compareTo] 判断是否有更高的权限. + * + * @see isOwner 判断权限是否为群主 + * @see isOperator 判断权限是否为管理员或群主 + * + * @see Member.isOwner 对 [Member] 的扩展函数, 判断此成员是否为群主 + * @see Member.isOperator 对 [Member] 的扩展函数, 判断此成员是否为管理员或群主 + * @see Member.isAdministrator 对 [Member] 的扩展函数, 判断此成员是否为管理员 */ enum class MemberPermission : Comparable { /** @@ -38,7 +45,6 @@ enum class MemberPermission : Comparable { /** * 权限等级. [OWNER] 为 2, [ADMINISTRATOR] 为 1, [MEMBER] 为 0 */ - @SinceMirai("0.32.0") val level: Int get() = ordinal } @@ -46,16 +52,19 @@ enum class MemberPermission : Comparable { /** * 判断权限是否为群主 */ +@InlineOnly inline fun MemberPermission.isOwner(): Boolean = this == MemberPermission.OWNER /** * 判断权限是否为管理员 */ +@InlineOnly inline fun MemberPermission.isAdministrator(): Boolean = this == MemberPermission.ADMINISTRATOR /** * 判断权限是否为管理员或群主 */ +@InlineOnly inline fun MemberPermission.isOperator(): Boolean = isAdministrator() || isOwner() @@ -85,7 +94,7 @@ class PermissionDeniedException : IllegalStateException { } /** - * 要求 [Bot] 在这个群里的权限为 [required], 否则抛出异常 [PermissionDeniedException] + * 要求 [Bot] 在这个群里的权限至少为 [required], 否则抛出异常 [PermissionDeniedException] * * @throws PermissionDeniedException */ @@ -95,7 +104,7 @@ inline fun Group.checkBotPermission( "Permission denied: required $required, got actual $botPermission for $bot in group $id" } ) { - if (botPermission != required) { + if (botPermission < required) { throw PermissionDeniedException(lazyMessage()) } } @@ -105,12 +114,9 @@ inline fun Group.checkBotPermission( * * @throws PermissionDeniedException */ +@Deprecated("use checkBotPermission", ReplaceWith("checkBotPermission(MemberPermission.ADMINISTRATOR)")) inline fun Group.checkBotPermissionOperator( crossinline lazyMessage: () -> String = { "Permission denied: required ${MemberPermission.ADMINISTRATOR} or ${MemberPermission.OWNER}, got actual $botPermission for $bot in group $id" } -) { - if (!botPermission.isOperator()) { - throw PermissionDeniedException(lazyMessage()) - } -} \ No newline at end of file +) = checkBotPermission(MemberPermission.ADMINISTRATOR, lazyMessage) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt deleted file mode 100644 index a5055deb8..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") - -package net.mamoe.mirai.contact - -import kotlinx.coroutines.CoroutineScope -import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.events.EventCancelledException -import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent -import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent -import net.mamoe.mirai.message.MessageReceipt -import net.mamoe.mirai.message.data.Message -import net.mamoe.mirai.utils.PlannedRemoval -import kotlin.jvm.JvmSynthetic - -/** - * QQ 对象. - * - * 自 0.39.0 起 mirai 引入 [User] 作为 [Friend] 和 [Member] 的父类, - * 以备将来支持仅 [Friend] 可用的 API, 如设置备注. - * - * 所有 API 均有二进制兼容. - * - * 请根据实际情况, 使用 [Friend] 或 [User] 替代. - */ -@PlannedRemoval("1.0.0") -@Deprecated( - "use Friend or Person instead", - replaceWith = ReplaceWith("Friend", "net.mamoe.mirai.contact.Friend"), - level = DeprecationLevel.ERROR -) -@Suppress("DEPRECATION_ERROR") -abstract class QQ : User(), CoroutineScope { - /** - * QQ 号码 - */ - abstract override val id: Long - - /** - * 昵称 - */ - abstract override val nick: String - - /** - * 头像下载链接 - */ - override val avatarUrl: String - get() = "http://q1.qlogo.cn/g?b=qq&nk=$id&s=640" - - /** - * 向这个对象发送消息. - * - * 单条消息最大可发送 4500 字符或 50 张图片. - * - * @see FriendMessageSendEvent 发送好友信息事件, cancellable - * @see GroupMessageSendEvent 发送群消息事件. cancellable - * - * @throws EventCancelledException 当发送消息事件被取消时抛出 - * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 - * @throws MessageTooLargeException 当消息过长时抛出 - * - * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) - */ - @JvmSynthetic - abstract override suspend fun sendMessage(message: Message): MessageReceipt -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/AddFriendResult.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/AddFriendResult.kt deleted file mode 100644 index 4d1440168..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/AddFriendResult.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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 - */ - -@file:JvmMultifileClass -@file:JvmName("BotHelperKt") -@file:Suppress("EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.data - -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName - - -@MiraiExperimentalAPI -@Suppress("ClassName") -sealed class AddFriendResult { - abstract class DONE internal constructor() : AddFriendResult() { - override fun toString(): String = "AddFriendResult(Done)" - } - - /** - * 对方拒绝添加好友 - */ - object REJECTED : AddFriendResult() { - override fun toString(): String = "AddFriendResult(Rejected)" - } - - /** - * 这个人已经是好友 - */ - object ALREADY_ADDED : DONE() { - override fun toString(): String = "AddFriendResult(AlreadyAdded)" - } - - /** - * 等待对方同意 - */ - object WAITING_FOR_APPROVAL : DONE() { - override fun toString(): String = "AddFriendResult(WaitingForApproval)" - } - - /** - * 成功添加 (只在对方设置为允许任何人直接添加为好友时才会获得这个结果) - */ - object ADDED : DONE() { - override fun toString(): String = "AddFriendResult(Added)" - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupActiveData.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupActiveData.kt index 117447a52..ec67b655f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupActiveData.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupActiveData.kt @@ -4,14 +4,12 @@ package net.mamoe.mirai.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.SinceMirai /** * 群统计信息 */ @MiraiExperimentalAPI -@SinceMirai("0.28.0") @Serializable data class GroupActiveData( diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/PreviousNameList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/PreviousNameList.kt deleted file mode 100644 index 3230baea1..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/PreviousNameList.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * 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.data - -import net.mamoe.mirai.utils.MiraiExperimentalAPI - -/** - * 曾用名列表 - * - * 曾用名可能是: - * - 昵称 - * - 共同群内的群名片 - */ -@MiraiExperimentalAPI -class PreviousNameList( - list: List -) : List by list { - override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ") -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Profile.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Profile.kt index 5f06f028f..1705578f7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Profile.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Profile.kt @@ -11,9 +11,7 @@ package net.mamoe.mirai.data -import io.ktor.util.date.GMTDate -import net.mamoe.mirai.utils.MiraiExperimentalAPI - +/* /** * 个人资料 */ @@ -61,4 +59,4 @@ enum class Gender(val value: Byte) { SECRET(0), MALE(1), FEMALE(2) -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt index afa67683b..edf0706d7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt @@ -70,7 +70,7 @@ interface Event { abstract class AbstractEvent : Event { @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "PropertyName") @get:JvmSynthetic // so Java user won't see it - @Deprecated("", level = DeprecationLevel.HIDDEN) + @Deprecated("prohibit illegal overrides", level = DeprecationLevel.HIDDEN) final override val DoNotImplementThisClassButExtendAbstractEvent: Nothing get() = throw Error("Shouldn't be reached") 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 560cf5c68..1423915f4 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 @@ -17,13 +17,11 @@ package net.mamoe.mirai.event import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.internal.* -import net.mamoe.mirai.message.ContactMessage -import net.mamoe.mirai.message.FriendMessage -import net.mamoe.mirai.message.GroupMessage -import net.mamoe.mirai.message.TempMessage +import net.mamoe.mirai.message.FriendMessageEvent +import net.mamoe.mirai.message.GroupMessageEvent +import net.mamoe.mirai.message.MessageEvent +import net.mamoe.mirai.message.TempMessageEvent import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.utils.PlannedRemoval -import kotlin.js.JsName import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic @@ -33,7 +31,7 @@ import kotlin.jvm.JvmSynthetic * 消息事件的处理器. * * 注: - * 接受者 T 为 [ContactMessage] + * 接受者 T 为 [MessageEvent] * 参数 String 为 转为字符串了的消息 ([Message.toString]) */ typealias MessageListener = @MessageDsl suspend T.(String) -> R @@ -49,7 +47,7 @@ typealias MessageListener = @MessageDsl suspend T.(String) -> R * @see subscribeFriendMessages */ @MessageDsl -open class MessageSubscribersBuilder( +open class MessageSubscribersBuilder( /** * 用于 [MessageListener] 无返回值的替代. */ @@ -233,7 +231,7 @@ open class MessageSubscribersBuilder( /** 如果是这个人发的消息. 消息目前只会是群消息 */ @MessageDsl - fun sentBy(name: String): ListeningFilter = content { this is GroupMessage && this.senderName == name } + fun sentBy(name: String): ListeningFilter = content { this is GroupMessageEvent && this.senderName == name } /** 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */ @MessageDsl @@ -249,36 +247,37 @@ open class MessageSubscribersBuilder( /** 如果是好友发来的消息 */ @MessageDsl - fun sentByFriend(onEvent: MessageListener): Ret = - content({ this is FriendMessage }) { onEvent(this as FriendMessage, it) } + fun sentByFriend(onEvent: MessageListener): Ret = + content({ this is FriendMessageEvent }) { onEvent(this as FriendMessageEvent, it) } /** 如果是好友发来的消息 */ @MessageDsl - fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage } + fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessageEvent } - /** 如果是好友发来的消息 */ + /** 如果是群临时会话消息 */ @MessageDsl - fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessage } + fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessageEvent } /** 如果是管理员或群主发的消息 */ @MessageDsl - fun sentByOperator(): ListeningFilter = content { this is GroupMessage && sender.permission.isOperator() } + fun sentByOperator(): ListeningFilter = content { this is GroupMessageEvent && sender.permission.isOperator() } /** 如果是管理员发的消息 */ @MessageDsl - fun sentByAdministrator(): ListeningFilter = content { this is GroupMessage && sender.permission.isAdministrator() } + fun sentByAdministrator(): ListeningFilter = + content { this is GroupMessageEvent && sender.permission.isAdministrator() } /** 如果是群主发的消息 */ @MessageDsl - fun sentByOwner(): ListeningFilter = content { this is GroupMessage && sender.isOwner() } + fun sentByOwner(): ListeningFilter = content { this is GroupMessageEvent && sender.isOwner() } /** 如果是来自这个群的消息 */ @MessageDsl - fun sentFrom(groupId: Long): ListeningFilter = content { this is GroupMessage && group.id == groupId } + fun sentFrom(groupId: Long): ListeningFilter = content { this is GroupMessageEvent && group.id == groupId } /** 如果是来自这个群的消息 */ @MessageDsl - fun sentFrom(group: Group): ListeningFilter = content { this is GroupMessage && group.id == group.id } + fun sentFrom(group: Group): ListeningFilter = content { this is GroupMessageEvent && group.id == group.id } /** [消息内容][Message.contentToString]包含目标为 [Bot] 的 [At] */ @MessageDsl @@ -440,79 +439,6 @@ open class MessageSubscribersBuilder( } return stub } - - /** - * 不考虑空格, [消息内容][Message.contentToString]以 [this] 开始则执行 [replier] 并将其返回值回复给发信对象. - * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他类型则 [Any.toString] 后回复 - */ - @PlannedRemoval("1.0.0") - @Deprecated("use startsWith on your own", replaceWith = ReplaceWith("startsWith(this, true, true, replier)")) - open infix fun String.startsWithReply(replier: @MessageDsl suspend M.(String) -> Any?): Ret { - val toCheck = this.trimStart() - return content({ it.trim().startsWith(toCheck) }, { - executeAndReply(this) { replier(this, it.trim().removePrefix(toCheck)) } - }) - } - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun contains(message: Message, onEvent: MessageListener): Ret { - return content({ this.message.any { it == message } }, onEvent) - } - - @JvmName("case1") - @JsName("case1") - @PlannedRemoval("1.0.0") - @Deprecated("use String.invoke", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this(block)")) - infix fun String.`->`(block: MessageListener): Ret { - return this(block) - } - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun containsAll( - vararg sub: String, - ignoreCase: Boolean = false, - trim: Boolean = true, - onEvent: MessageListener - ): Ret = containsAllImpl(sub, ignoreCase = ignoreCase, trim = trim).invoke(onEvent) - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun containsAny( - vararg sub: String, - ignoreCase: Boolean = false, - trim: Boolean = true, - onEvent: MessageListener - ): Ret = containsAnyImpl(*sub, ignoreCase = ignoreCase, trim = trim).invoke(onEvent) - - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun sentBy(name: String, onEvent: MessageListener): Ret = - content({ (this as? GroupMessage)?.senderName == name }, onEvent) - - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun sentByOperator(onEvent: MessageListener): Ret = - content({ this is GroupMessage && this.sender.isOperator() }, onEvent) - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun sentByAdministrator(onEvent: MessageListener): Ret = - content({ this is GroupMessage && this.sender.isAdministrator() }, onEvent) - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun sentByOwner(onEvent: MessageListener): Ret = - content({ this is GroupMessage && this.sender.isOwner() }, onEvent) - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - fun sentFrom(groupId: Long, onEvent: MessageListener): Ret = - content({ this is GroupMessage && this.group.id == groupId }) { onEvent(this as GroupMessage, it) } - } /** 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 53432d481..e5edf02fe 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 @@ -25,7 +25,9 @@ import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.qqandroid.network.Packet -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.internal.runBlocking import kotlin.jvm.JvmField import kotlin.jvm.JvmName @@ -75,7 +77,6 @@ sealed class BotOfflineEvent : BotEvent, AbstractEvent() { /** * 服务器主动要求更换另一个服务器 */ - @SinceMirai("0.37.1") data class RequireReconnect(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent } @@ -131,7 +132,6 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() { * 消息内部 id. * @see MessageSource.id */ - @SinceMirai("0.39.0") abstract val messageInternalId: Int /** @@ -148,7 +148,7 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() { override val messageInternalId: Int, override val messageTime: Int, /** - * 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id] + * 撤回操作人, 可能为 [Bot.uin] 或好友的 [User.id] */ val operator: Long ) : MessageRecallEvent(), Packet { @@ -235,20 +235,17 @@ sealed class ImageUploadEvent : BotEvent, BotActiveEvent, AbstractEvent() { /** * 机器人被踢出群或在其他客户端主动退出一个群. 在事件广播前 [Bot.groups] 就已删除这个群. */ -@SinceMirai("0.36.0") sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent() { abstract val group: Group /** * 机器人主动退出一个群. */ - @SinceMirai("0.37.0") data class Active(override val group: Group) : BotLeaveEvent() /** * 机器人被管理员或群主踢出群. 暂不支持获取操作人 */ - @SinceMirai("0.37.0") data class Kick(override val group: Group) : BotLeaveEvent() override val bot: Bot get() = group.bot @@ -321,7 +318,6 @@ data class GroupNameChangeEvent( /** * 操作人. 为 null 时则是机器人操作 */ - @SinceMirai("0.37.3") override val operator: Member? ) : GroupSettingChangeEvent, Packet, GroupOperableEvent, AbstractEvent() { @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @@ -409,13 +405,11 @@ sealed class MemberJoinEvent(override val member: Member) : GroupMemberEvent, Bo /** * 被邀请加入群 */ - @SinceMirai("0.36.0") data class Invite(override val member: Member) : MemberJoinEvent(member) /** * 成员主动加入群 */ - @SinceMirai("0.36.0") data class Active(override val member: Member) : MemberJoinEvent(member) } @@ -451,7 +445,6 @@ sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() { /** * [Bot] 被邀请加入一个群. */ -@SinceMirai("0.39.4") data class BotInvitedJoinGroupRequestEvent( override val bot: Bot, /** @@ -494,7 +487,6 @@ data class BotInvitedJoinGroupRequestEvent( /** * 一个账号请求加入群事件, [Bot] 在此群中是管理员或群主. */ -@SinceMirai("0.35.0") data class MemberJoinRequestEvent( override val bot: Bot, /** @@ -566,17 +558,8 @@ data class MemberCardChangeEvent( */ val new: String, - override val member: Member, - - /** - * 此事件无法确定操作人, 将在未来版本删除 - */ - @PlannedRemoval("1.0.0") - @Deprecated("operator is always unknown", level = DeprecationLevel.ERROR) - override val operator: Member? -) : GroupMemberEvent, Packet, AbstractEvent(), - @Deprecated("operator is always unknown", level = DeprecationLevel.ERROR) - GroupOperableEvent + override val member: Member +) : GroupMemberEvent, Packet, AbstractEvent() /** * 成员群头衔改动. 一定为群主操作 @@ -623,6 +606,8 @@ data class MemberPermissionChangeEvent( /** * 群成员被禁言事件. 被禁言的成员都不可能是机器人本人 + * + * @see BotMuteEvent 机器人被禁言的事件 */ data class MemberMuteEvent( override val member: Member, @@ -635,6 +620,8 @@ data class MemberMuteEvent( /** * 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人 + * + * @see BotUnmuteEvent 机器人被取消禁言的事件 */ data class MemberUnmuteEvent( override val member: Member, @@ -655,65 +642,36 @@ data class MemberUnmuteEvent( /** * 好友昵称改变事件. 目前仅支持解析 (来自 PC 端的修改). */ -@SinceMirai("0.36.0") data class FriendRemarkChangeEvent( override val bot: Bot, - val friend: Friend, + override val friend: Friend, val newName: String -) : BotEvent, Packet, AbstractEvent() { - - @PlannedRemoval("1.0.0") - @Deprecated("", level = DeprecationLevel.HIDDEN) - @get:JvmSynthetic - @get:JvmName("getFriend") - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") - val friendDeprecated: QQ - get() = friend -} +) : FriendEvent, Packet, AbstractEvent() /** * 成功添加了一个新好友的事件 */ -@SinceMirai("0.36.0") data class FriendAddEvent( /** * 新好友. 已经添加到 [Bot.friends] */ - val friend: Friend -) : BotEvent, Packet, AbstractEvent() { + override val friend: Friend +) : FriendEvent, Packet, AbstractEvent() { override val bot: Bot get() = friend.bot - - @PlannedRemoval("1.0.0") - @Deprecated("", level = DeprecationLevel.HIDDEN) - @get:JvmSynthetic - @get:JvmName("getFriend") - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") - val friendDeprecated: QQ - get() = friend } /** * 好友已被删除的事件. */ -@SinceMirai("0.36.0") data class FriendDeleteEvent( - val friend: Friend -) : BotEvent, Packet, AbstractEvent() { + override val friend: Friend +) : FriendEvent, Packet, AbstractEvent() { override val bot: Bot get() = friend.bot - - @PlannedRemoval("1.0.0") - @Deprecated("", level = DeprecationLevel.HIDDEN) - @get:JvmSynthetic - @get:JvmName("getFriend") - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") - val friendDeprecated: QQ - get() = friend } /** * 一个账号请求添加机器人为好友的事件 */ -@SinceMirai("0.35.0") data class NewFriendRequestEvent( override val bot: Bot, /** @@ -725,7 +683,7 @@ data class NewFriendRequestEvent( */ val message: String, /** - * 请求人 [QQ.id] + * 请求人 [User.id] */ val fromId: Long, /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt index 1019a6f1a..613f67861 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt @@ -15,8 +15,6 @@ import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.event.Event import net.mamoe.mirai.utils.MiraiExperimentalAPI -import kotlin.jvm.JvmName -import kotlin.jvm.JvmSynthetic /** * 有关一个 [Bot] 的事件 @@ -90,11 +88,4 @@ interface FriendEvent : BotEvent { val friend: Friend override val bot: Bot get() = friend.bot - - @Deprecated("", level = DeprecationLevel.HIDDEN) - @get:JvmSynthetic - @get:JvmName("getFriend") - @Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR") - val friendDeprecated: net.mamoe.mirai.contact.QQ - get() = friend } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/messageSubscribersInternal.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/messageSubscribersInternal.kt index 36393ae31..ad72d2aaa 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/messageSubscribersInternal.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/messageSubscribersInternal.kt @@ -12,7 +12,7 @@ package net.mamoe.mirai.event.internal import net.mamoe.mirai.event.MessageDsl import net.mamoe.mirai.event.MessageListener import net.mamoe.mirai.event.MessageSubscribersBuilder -import net.mamoe.mirai.message.ContactMessage +import net.mamoe.mirai.message.MessageEvent /* @@ -20,13 +20,13 @@ import net.mamoe.mirai.message.ContactMessage */ @MessageDsl -internal fun MessageSubscribersBuilder.content( +internal fun MessageSubscribersBuilder.content( filter: M.(String) -> Boolean, onEvent: MessageListener ): Ret = subscriber(filter) { onEvent(this, it) } -internal fun MessageSubscribersBuilder.endsWithImpl( +internal fun MessageSubscribersBuilder.endsWithImpl( suffix: String, removeSuffix: Boolean = true, trim: Boolean = true, @@ -46,7 +46,7 @@ internal fun MessageSubscribersBuilder MessageSubscribersBuilder.startsWithImpl( +internal fun MessageSubscribersBuilder.startsWithImpl( prefix: String, removePrefix: Boolean = true, trim: Boolean = true, @@ -64,7 +64,7 @@ internal fun MessageSubscribersBuilder MessageSubscribersBuilder.containsAllImpl( +internal fun MessageSubscribersBuilder.containsAllImpl( sub: Array, ignoreCase: Boolean = false, trim: Boolean = true @@ -76,7 +76,7 @@ internal fun MessageSubscribersBuilder it.contains(toCheck, ignoreCase = ignoreCase) } } } -internal fun MessageSubscribersBuilder.containsAnyImpl( +internal fun MessageSubscribersBuilder.containsAnyImpl( vararg sub: String, ignoreCase: Boolean = false, trim: Boolean = true @@ -86,7 +86,7 @@ internal fun MessageSubscribersBuilder it.contains(toCheck, ignoreCase = ignoreCase) } } } else content { sub.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } -internal fun MessageSubscribersBuilder.caseImpl( +internal fun MessageSubscribersBuilder.caseImpl( equals: String, ignoreCase: Boolean = false, trim: Boolean = true @@ -99,7 +99,7 @@ internal fun MessageSubscribersBuilder MessageSubscribersBuilder.containsImpl( +internal fun MessageSubscribersBuilder.containsImpl( sub: String, ignoreCase: Boolean = false, trim: Boolean = true, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt index 4ee8fadd4..1f8153180 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.event import kotlinx.coroutines.* -import net.mamoe.mirai.utils.SinceMirai import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmSynthetic @@ -25,12 +24,13 @@ import kotlin.reflect.KClass * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * * @see asyncFromEvent 本函数的异步版本 + * @see subscribe 普通地监听一个事件 + * @see nextEvent 挂起当前协程, 并获取下一个事件实例 * * @throws TimeoutCancellationException 在超时后抛出. * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 */ @JvmSynthetic -@SinceMirai("0.39.0") suspend inline fun syncFromEvent( timeoutMillis: Long = -1, crossinline mapper: suspend E.(E) -> R? @@ -57,10 +57,12 @@ suspend inline fun syncFromEvent( * @return 超时返回 `null`, 否则返回 [mapper] 返回的第一个非 `null` 值. * * @see asyncFromEvent 本函数的异步版本 + * @see subscribe 普通地监听一个事件 + * @see nextEvent 挂起当前协程, 并获取下一个事件实例 + * * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 */ @JvmSynthetic -@SinceMirai("0.39.0") suspend inline fun syncFromEventOrNull( timeoutMillis: Long, crossinline mapper: suspend E.(E) -> R? @@ -80,10 +82,14 @@ suspend inline fun syncFromEventOrNull( * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param coroutineContext 额外的 [CoroutineContext] * @param mapper 过滤转换器. 返回非 `null` 则代表得到了需要的值. [syncFromEvent] 会返回这个值 + * + * @see syncFromEvent + * @see asyncFromEvent + * @see subscribe 普通地监听一个事件 + * @see nextEvent 挂起当前协程, 并获取下一个事件实例 */ @JvmSynthetic @Suppress("DeferredIsResult") -@SinceMirai("0.39.0") inline fun CoroutineScope.asyncFromEventOrNull( timeoutMillis: Long, coroutineContext: CoroutineContext = EmptyCoroutineContext, @@ -104,10 +110,14 @@ inline fun CoroutineScope.asyncFromEventOrNull( * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param coroutineContext 额外的 [CoroutineContext] * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 + * + * @see syncFromEvent + * @see asyncFromEventOrNull + * @see subscribe 普通地监听一个事件 + * @see nextEvent 挂起当前协程, 并获取下一个事件实例 */ @JvmSynthetic @Suppress("DeferredIsResult") -@SinceMirai("0.39.0") inline fun CoroutineScope.asyncFromEvent( timeoutMillis: Long = -1, coroutineContext: CoroutineContext = EmptyCoroutineContext, @@ -132,9 +142,12 @@ internal suspend inline fun syncFromEventImpl( crossinline mapper: suspend E.(E) -> R? ): R = suspendCancellableCoroutine { cont -> coroutineScope.subscribe(eventClass) { - cont.resumeWith(kotlin.runCatching { - mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING - }) + try { + cont.resumeWith(kotlin.runCatching { + mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING + }) + } catch (e: Exception) { + } return@subscribe ListeningStatus.STOPPED } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/nextEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/nextEvent.kt new file mode 100644 index 000000000..a15636dd2 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/nextEvent.kt @@ -0,0 +1,106 @@ +/* + * 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.event + +import kotlinx.coroutines.* +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.BotEvent +import kotlin.coroutines.resume +import kotlin.jvm.JvmSynthetic +import kotlin.reflect.KClass + + +/** + * 挂起当前协程, 直到监听到事件 [E] 的广播, 返回这个事件实例. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制. + * + * @see subscribe 普通地监听一个事件 + * @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值 + * + * @throws TimeoutCancellationException 在超时后抛出. + */ +@JvmSynthetic +suspend inline fun nextEvent( + timeoutMillis: Long = -1 +): E { + require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } + return withTimeoutOrCoroutineScope(timeoutMillis) { + nextEventImpl(E::class, this) + } +} + +/** + * 挂起当前协程, 直到监听到事件 [E] 的广播, 返回这个事件实例. + * 将筛选 [BotEvent.bot] 与 [this] 相等的事件. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制. + * + * @see subscribe 普通地监听一个事件 + * @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值 + * + * @throws TimeoutCancellationException 在超时后抛出. + */ +@JvmSynthetic +suspend inline fun Bot.nextEvent( + timeoutMillis: Long = -1 +): E { + require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } + return withTimeoutOrCoroutineScope(timeoutMillis) { + nextBotEventImpl(this@nextEvent, E::class, this) + } +} + + +@JvmSynthetic +@PublishedApi +internal suspend inline fun nextEventImpl( + eventClass: KClass, + coroutineScope: CoroutineScope +): E = suspendCancellableCoroutine { cont -> + coroutineScope.subscribe(eventClass) { + try { + cont.resume(this) + } catch (e: Exception) { + } + return@subscribe ListeningStatus.STOPPED + } +} + +@JvmSynthetic +@PublishedApi +internal suspend inline fun nextBotEventImpl( + bot: Bot, + eventClass: KClass, + coroutineScope: CoroutineScope +): E = suspendCancellableCoroutine { cont -> + coroutineScope.subscribe(eventClass) { + try { + if (this.bot == bot) cont.resume(this) + } catch (e: Exception) { + } + return@subscribe ListeningStatus.STOPPED + } +} + +@JvmSynthetic +@PublishedApi +internal suspend inline fun withTimeoutOrCoroutineScope( + timeoutMillis: Long, + noinline block: suspend CoroutineScope.() -> R +): R { + require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } + + return if (timeoutMillis == -1L) { + coroutineScope(block) + } else { + withTimeout(timeoutMillis, block) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/select.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/select.kt index c47991b10..8dd5c58d7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/select.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/select.kt @@ -12,13 +12,12 @@ package net.mamoe.mirai.event import kotlinx.coroutines.* -import net.mamoe.mirai.message.ContactMessage +import net.mamoe.mirai.message.MessageEvent import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.isContextIdenticalWith import net.mamoe.mirai.message.nextMessage import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.SinceMirai import kotlin.experimental.ExperimentalTypeInference import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -59,9 +58,8 @@ import kotlin.jvm.JvmSynthetic * @see subscribeMessages * @see nextMessage 挂起协程并等待下一条消息 */ -@SinceMirai("0.29.0") @Suppress("unused") -suspend inline fun T.whileSelectMessages( +suspend inline fun T.whileSelectMessages( timeoutMillis: Long = -1, filterContext: Boolean = true, crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit @@ -72,9 +70,8 @@ suspend inline fun T.whileSelectMessages( */ @OptIn(ExperimentalTypeInference::class) @MiraiExperimentalAPI -@SinceMirai("0.29.0") @JvmName("selectMessages1") -suspend inline fun T.selectMessagesUnit( +suspend inline fun T.selectMessagesUnit( timeoutMillis: Long = -1, filterContext: Boolean = true, crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit.() -> Unit @@ -101,10 +98,9 @@ suspend inline fun T.selectMessagesUnit( * * @see nextMessage 挂起协程并等待下一条消息 */ -@SinceMirai("0.29.0") @Suppress("unused") // false positive // @BuilderInference // https://youtrack.jetbrains.com/issue/KT-37716 -suspend inline fun T.selectMessages( +suspend inline fun T.selectMessages( timeoutMillis: Long = -1, filterContext: Boolean = true, // @BuilderInference @@ -120,8 +116,7 @@ suspend inline fun T.selectMessages( * @see MessageSelectBuilderUnit 查看上层 API */ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") -@SinceMirai("0.29.0") -abstract class MessageSelectBuilder @PublishedApi internal constructor( +abstract class MessageSelectBuilder @PublishedApi internal constructor( ownerMessagePacket: M, stub: Any?, subscriber: (M.(String) -> Boolean, MessageListener) -> Unit @@ -185,9 +180,6 @@ abstract class MessageSelectBuilder @PublishedApi interna @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun Regex.findingReply(replier: suspend M.(MatchResult) -> Any?) = error("prohibited") - @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) - override fun String.startsWithReply(replier: suspend M.(String) -> Any?) = error("prohibited") - @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun String.endsWithReply(replier: suspend M.(String) -> Any?) = error("prohibited") @@ -238,8 +230,7 @@ abstract class MessageSelectBuilder @PublishedApi interna * * @see MessageSubscribersBuilder 查看上层 API */ -@SinceMirai("0.29.0") -abstract class MessageSelectBuilderUnit @PublishedApi internal constructor( +abstract class MessageSelectBuilderUnit @PublishedApi internal constructor( private val ownerMessagePacket: M, stub: Any?, subscriber: (M.(String) -> Boolean, MessageListener) -> Unit @@ -455,7 +446,7 @@ class MessageSelectionTimeoutException : RuntimeException() @JvmSynthetic @PublishedApi -internal suspend inline fun withTimeoutOrCoroutineScope( +internal suspend inline fun withSilentTimeoutOrCoroutineScope( timeoutMillis: Long, noinline block: suspend CoroutineScope.() -> R ): R { @@ -483,13 +474,13 @@ internal val ExceptionHandlerIgnoringCancellationException = CoroutineExceptionH @PublishedApi @BuilderInference @OptIn(ExperimentalTypeInference::class) -internal suspend inline fun T.selectMessagesImpl( +internal suspend inline fun T.selectMessagesImpl( timeoutMillis: Long = -1, isUnit: Boolean, filterContext: Boolean = true, @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit.() -> Unit -): R = withTimeoutOrCoroutineScope(timeoutMillis) { +): R = withSilentTimeoutOrCoroutineScope(timeoutMillis) { var deferred: CompletableDeferred? = CompletableDeferred() coroutineContext[Job]!!.invokeOnCompletion { deferred?.cancel() @@ -509,7 +500,7 @@ internal suspend inline fun T.selectMessagesImpl SELECT_MESSAGE_STUB, outside ) { - override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope + override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope override fun obtainCurrentDeferred(): CompletableDeferred? = deferred override fun default(onEvent: MessageListener) { defaultListeners += onEvent @@ -525,7 +516,7 @@ internal suspend inline fun T.selectMessagesImpl SELECT_MESSAGE_STUB, outside ) { - override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope + override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope override fun obtainCurrentDeferred(): CompletableDeferred? = deferred override fun default(onEvent: MessageListener) { defaultListeners += onEvent @@ -582,11 +573,11 @@ internal suspend inline fun T.selectMessagesImpl @Suppress("unused") @PublishedApi -internal suspend inline fun T.whileSelectMessagesImpl( +internal suspend inline fun T.whileSelectMessagesImpl( timeoutMillis: Long = -1, filterContext: Boolean = true, crossinline selectBuilder: @MessageDsl MessageSelectBuilder.() -> Unit -) = withTimeoutOrCoroutineScope(timeoutMillis) { +) = withSilentTimeoutOrCoroutineScope(timeoutMillis) { var deferred: CompletableDeferred? = CompletableDeferred() coroutineContext[Job]!!.invokeOnCompletion { deferred?.cancel() @@ -605,7 +596,7 @@ internal suspend inline fun T.whileSelectMessagesIm SELECT_MESSAGE_STUB, outside ) { - override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope + override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope override fun obtainCurrentDeferred(): CompletableDeferred? = deferred override fun default(onEvent: MessageListener) { defaultListeners += onEvent diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt index b321dfc59..7d4e60e19 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt @@ -17,18 +17,17 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.BotEvent -import net.mamoe.mirai.message.ContactMessage -import net.mamoe.mirai.message.FriendMessage -import net.mamoe.mirai.message.GroupMessage -import net.mamoe.mirai.message.TempMessage -import net.mamoe.mirai.utils.SinceMirai +import net.mamoe.mirai.message.FriendMessageEvent +import net.mamoe.mirai.message.GroupMessageEvent +import net.mamoe.mirai.message.MessageEvent +import net.mamoe.mirai.message.TempMessageEvent import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> +typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> /** * 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. @@ -47,7 +46,7 @@ fun CoroutineScope.subscribeMessages( } return MessagePacketSubscribersBuilder(Unit) - { filter, messageListener: MessageListener -> + { filter, messageListener: MessageListener -> // subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [messageListener] // messageListener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块. subscribeAlways(coroutineContext, concurrencyKind) { @@ -59,7 +58,7 @@ fun CoroutineScope.subscribeMessages( }.run(listeners) } -typealias GroupMessageSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> +typealias GroupMessageSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> /** * 订阅来自所有 [Bot] 的所有群消息事件 @@ -84,7 +83,7 @@ fun CoroutineScope.subscribeGroupMessages( }.run(listeners) } -typealias FriendMessageSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> +typealias FriendMessageSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> /** * 订阅来自所有 [Bot] 的所有好友消息事件 @@ -109,7 +108,7 @@ fun CoroutineScope.subscribeFriendMessages( }.run(listeners) } -typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> +typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder, Unit, Unit> /** * 订阅来自所有 [Bot] 的所有临时会话消息事件 @@ -211,7 +210,6 @@ fun Bot.subscribeFriendMessages( * * @see CoroutineScope.incoming 打开一个指定事件的接收通道 */ -@SinceMirai("0.35.0") @OptIn(ExperimentalContracts::class) fun Bot.subscribeTempMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt index bc1839dea..797332601 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt @@ -206,7 +206,6 @@ inline fun CoroutineScope.subscribe( * * @see CoroutineScope.subscribe */ -@SinceMirai("0.38.0") fun CoroutineScope.subscribe( eventClass: KClass, coroutineContext: CoroutineContext = EmptyCoroutineContext, @@ -238,7 +237,6 @@ inline fun CoroutineScope.subscribeAlways( /** * @see CoroutineScope.subscribeAlways */ -@SinceMirai("0.38.0") fun CoroutineScope.subscribeAlways( eventClass: KClass, coroutineContext: CoroutineContext = EmptyCoroutineContext, @@ -271,7 +269,6 @@ inline fun CoroutineScope.subscribeOnce( /** * @see CoroutineScope.subscribeOnce */ -@SinceMirai("0.38.0") fun CoroutineScope.subscribeOnce( eventClass: KClass, coroutineContext: CoroutineContext = EmptyCoroutineContext, @@ -308,7 +305,6 @@ inline fun Bot.subscribe( * * @see Bot.subscribe */ -@SinceMirai("0.38.0") fun Bot.subscribe( eventClass: KClass, coroutineContext: CoroutineContext = EmptyCoroutineContext, @@ -345,7 +341,6 @@ inline fun Bot.subscribeAlways( * * @see Bot.subscribeAlways */ -@SinceMirai("0.38.0") fun Bot.subscribeAlways( eventClass: KClass, coroutineContext: CoroutineContext = EmptyCoroutineContext, @@ -376,7 +371,6 @@ inline fun Bot.subscribeOnce( * * @see Bot.subscribeOnce */ -@SinceMirai("0.38.0") fun Bot.subscribeOnce( eventClass: KClass, coroutineContext: CoroutineContext = EmptyCoroutineContext, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt index b0b3daa5e..d21d779f2 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt @@ -14,7 +14,6 @@ import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.data.* import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.WeakRef /** @@ -73,7 +72,6 @@ interface LowLevelBotAPIAccessor { * 获取群公告列表 * @param page 页码 */ - @SinceMirai("0.28.0") @LowLevelAPI @MiraiExperimentalAPI suspend fun _lowLevelGetAnnouncements(groupId: Long, page: Int = 1, amount: Int = 10): GroupAnnouncementList @@ -83,7 +81,6 @@ interface LowLevelBotAPIAccessor { * * @return 公告的fid */ - @SinceMirai("0.28.0") @LowLevelAPI @MiraiExperimentalAPI suspend fun _lowLevelSendAnnouncement(groupId: Long, announcement: GroupAnnouncement): String @@ -93,7 +90,6 @@ interface LowLevelBotAPIAccessor { * 删除群公告 * @param fid [GroupAnnouncement.fid] */ - @SinceMirai("0.28.0") @LowLevelAPI @MiraiExperimentalAPI suspend fun _lowLevelDeleteAnnouncement(groupId: Long, fid: String) @@ -102,7 +98,6 @@ interface LowLevelBotAPIAccessor { * 获取一条群公告 * @param fid [GroupAnnouncement.fid] */ - @SinceMirai("0.28.0") @LowLevelAPI @MiraiExperimentalAPI suspend fun _lowLevelGetAnnouncement(groupId: Long, fid: String): GroupAnnouncement @@ -111,7 +106,6 @@ interface LowLevelBotAPIAccessor { /** * 获取群活跃信息 */ - @SinceMirai("0.29.0") @LowLevelAPI @MiraiExperimentalAPI suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/ContactMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/ContactMessage.kt deleted file mode 100644 index 6963694a0..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/ContactMessage.kt +++ /dev/null @@ -1,438 +0,0 @@ -/* - * 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 - */ - -@file:Suppress( - "EXPERIMENTAL_UNSIGNED_LITERALS", - "EXPERIMENTAL_API_USAGE", - "unused", - "INVISIBLE_REFERENCE", - "INVISIBLE_MEMBER" -) -@file:OptIn(MiraiInternalAPI::class) - -package net.mamoe.mirai.message - -import kotlinx.coroutines.Deferred -import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.async -import kotlinx.coroutines.io.ByteReadChannel -import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.* -import net.mamoe.mirai.event.* -import net.mamoe.mirai.event.events.BotEvent -import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.qqandroid.network.Packet -import net.mamoe.mirai.utils.* -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext -import kotlin.jvm.JvmName -import kotlin.jvm.JvmSynthetic - -/** - * 一条消息事件. - * 它是一个 [BotEvent], 因此可以被 [监听][Bot.subscribe] - * - * 支持的消息类型: - * [GroupMessage] - * [FriendMessage] - * - * @see isContextIdenticalWith 判断语境是否相同 - */ -@Suppress("DEPRECATION") -@SinceMirai("0.32.0") -abstract class ContactMessage : MessagePacket(), BotEvent - -/** - * 一条从服务器接收到的消息事件. - * 请查看各平台的 `actual` 实现的说明. - */ -@Suppress("DEPRECATION") -@Deprecated( - message = "use ContactMessage", - replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage") -) -expect abstract class MessagePacket constructor() : - MessagePacketBase - -/** - * 仅内部使用, 请使用 [ContactMessage] - */ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构 -@Deprecated( - message = "use ContactMessage", - replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage") -) -@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") -abstract class MessagePacketBase : Packet, BotEvent, AbstractEvent() { - /** - * 接受到这条消息的 - */ - @WeakRefProperty - abstract override val bot: Bot - - /** - * 消息事件主体. - * - * 对于好友消息, 这个属性为 [QQ] 的实例, 与 [sender] 引用相同; - * 对于群消息, 这个属性为 [Group] 的实例, 与 [GroupMessage.group] 引用相同 - * - * 在回复消息时, 可通过 [subject] 作为回复对象 - */ - @WeakRefProperty - abstract val subject: TSubject - - /** - * 发送人. - * - * 在好友消息时为 [QQ] 的实例, 在群消息时为 [Member] 的实例 - */ - @WeakRefProperty - abstract val sender: TSender - - abstract val senderName: String - - /** - * 消息内容 - */ - abstract val message: MessageChain - - /** - * 消息发送时间 (由服务器提供) - */ - @SinceMirai("0.39.0") - abstract val time: Int - - /** - * 消息源 - */ - open val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming - - // region 发送 Message - - /** - * 给这个消息事件的主体发送消息 - * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 - * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 - */ - suspend inline fun reply(message: Message): MessageReceipt = - subject.sendMessage(message.asMessageChain()) as MessageReceipt - - suspend inline fun reply(plain: String): MessageReceipt = - subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt - // endregion - - // region 图片 - suspend inline fun ExternalImage.upload(): Image = this.upload(subject) - - suspend inline fun ExternalImage.send(): MessageReceipt = this.sendTo(subject) - - suspend inline fun Image.send(): MessageReceipt = this.sendTo(subject) - suspend inline fun Message.send(): MessageReceipt = this.sendTo(subject) - suspend inline fun String.send(): MessageReceipt = this.toMessage().sendTo(subject) - // endregion - - - // region 引用回复 - /** - * 给这个消息事件的主体发送引用回复消息 - * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 - * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 - */ - suspend inline fun quoteReply(message: MessageChain): MessageReceipt = - reply(this.message.quote() + message) - - suspend inline fun quoteReply(message: Message): MessageReceipt = reply(this.message.quote() + message) - suspend inline fun quoteReply(plain: String): MessageReceipt = reply(this.message.quote() + plain) - - @JvmName("reply2") - suspend inline fun String.quoteReply(): MessageReceipt = quoteReply(this) - - @JvmName("reply2") - suspend inline fun Message.quoteReply(): MessageReceipt = quoteReply(this) - - @JvmName("reply2") - suspend inline fun MessageChain.quoteReply(): MessageReceipt = quoteReply(this) - - // endregion - - inline operator fun get(at: Message.Key): M { - return this.message[at] - } - - inline fun At.isBot(): Boolean = target == bot.id - - // endregion - - // region 下载图片 - - - /** - * 获取图片下载链接 - * - * @return "http://gchat.qpic.cn/gchatpic_new/..." - */ - suspend inline fun Image.url(): String = bot.queryImageUrl(this@url) - - /** - * 获取图片下载链接并开始下载. - * - * @see ByteReadChannel.copyAndClose - * @see ByteReadChannel.copyTo - */ - @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION") - @PlannedRemoval("1.0.0") - @Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING) - suspend inline fun Image.channel(): ByteReadChannel = bot.openChannel(this) - // endregion - - - @PlannedRemoval("1.0.0") - @Deprecated("use reply(String) for clear semantics", ReplaceWith("reply(this)"), - DeprecationLevel.ERROR) - @JvmName("reply1") - suspend inline fun String.reply(): MessageReceipt = reply(this) - - @PlannedRemoval("1.0.0") - @Deprecated("use reply(String) for clear semantics", ReplaceWith("reply(this)"), - level = DeprecationLevel.ERROR) - @JvmName("reply1") - suspend inline fun Message.reply(): MessageReceipt = reply(this) - - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @get:JvmSynthetic - @get:JvmName("getSender") - @Suppress("DEPRECATION_ERROR", "INAPPLICABLE_JVM_NAME") - val senderDeprecated: QQ - get() = sender as QQ - - @Suppress("DEPRECATION_ERROR") - @PlannedRemoval("1.0.0") - @Deprecated("removed", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("At(this as? Member ?: error(\"`QQ.at` can only be used in GroupMessage\"))", - "net.mamoe.mirai.message.data.At", - "net.mamoe.mirai.contact.Member")) - fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage")) - - @PlannedRemoval("1.0.0") - @Deprecated("removed", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("(this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error(\"`At.member` can only be used in GroupMessage\")")) - fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) - ?: error("`At.member` can only be used in GroupMessage") -} - -/** - * 判断两个 [MessagePacket] 的 [MessagePacket.sender] 和 [MessagePacket.subject] 是否相同 - */ -@SinceMirai("0.29.0") -fun ContactMessage.isContextIdenticalWith(another: ContactMessage): Boolean { - return this.sender == another.sender && this.subject == another.subject && this.bot == another.bot -} - -/** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessagePacket] - * - * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. - * - * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 - * - * @see syncFromEvent - */ -@JvmSynthetic -suspend inline fun P.nextMessage( - timeoutMillis: Long = -1, - crossinline filter: suspend P.(P) -> Boolean -): MessageChain { - return syncFromEvent(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) } - }.message -} - -/** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessagePacket] - * - * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. - * - * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 - * @return 消息链. 超时时返回 `null` - * - * @see syncFromEventOrNull - */ -@JvmSynthetic -suspend inline fun P.nextMessageOrNull( - timeoutMillis: Long = -1, - crossinline filter: suspend P.(P) -> Boolean -): MessageChain? { - return syncFromEventOrNull(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) } - }?.message -} - -/** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] - * - * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * - * @throws TimeoutCancellationException - * - * @see syncFromEvent - */ -@JvmSynthetic -suspend inline fun P.nextMessage( - timeoutMillis: Long = -1 -): MessageChain { - return syncFromEvent(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessage) } - }.message -} - -/** - * @see nextMessage - * @throws TimeoutCancellationException - */ -@JvmSynthetic -inline fun P.nextMessageAsync( - timeoutMillis: Long = -1, - coroutineContext: CoroutineContext = EmptyCoroutineContext -): Deferred { - return this.bot.async(coroutineContext) { - syncFromEvent(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageAsync) } - }.message - } -} - -/** - * @see nextMessage - */ -@JvmSynthetic -inline fun P.nextMessageAsync( - timeoutMillis: Long = -1, - coroutineContext: CoroutineContext = EmptyCoroutineContext, - crossinline filter: suspend P.(P) -> Boolean -): Deferred { - return this.bot.async(coroutineContext) { - syncFromEvent(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageAsync) } - .takeIf { filter(this, this) } - }.message - } -} - -/** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] - * - * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. - * - * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * @return 消息链. 超时时返回 `null` - * - * @see syncFromEventOrNull - */ -@JvmSynthetic -suspend inline fun P.nextMessageOrNull( - timeoutMillis: Long = -1 -): MessageChain? { - return syncFromEventOrNull(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) } - }?.message -} - -/** - * @see nextMessageOrNull - */ -@JvmSynthetic -inline fun P.nextMessageOrNullAsync( - timeoutMillis: Long = -1, - coroutineContext: CoroutineContext = EmptyCoroutineContext -): Deferred { - return this.bot.async(coroutineContext) { - syncFromEventOrNull(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageOrNullAsync) } - }?.message - } -} - -/** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] - * - * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. - * - * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * - * @see syncFromEvent - * @see whileSelectMessages - * @see selectMessages - */ -@JvmSynthetic -suspend inline fun ContactMessage.nextMessageContaining( - timeoutMillis: Long = -1 -): M { - return syncFromEvent(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageContaining) } - .takeIf { this.message.anyIsInstance() } - }.message.firstIsInstance() -} - -@JvmSynthetic -inline fun ContactMessage.nextMessageContainingAsync( - timeoutMillis: Long = -1, - coroutineContext: CoroutineContext = EmptyCoroutineContext -): Deferred { - return this.bot.async(coroutineContext) { - @Suppress("RemoveExplicitTypeArguments") - syncFromEvent(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) } - .takeIf { this.message.anyIsInstance() } - }.message.firstIsInstance() - } -} - -/** - * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同并含有 [M] 类型的消息的 [MessagePacket] - * - * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. - * - * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 - * @return 指定类型的消息. 超时时返回 `null` - * - * @see syncFromEventOrNull - */ -@JvmSynthetic -suspend inline fun ContactMessage.nextMessageContainingOrNull( - timeoutMillis: Long = -1 -): M? { - return syncFromEventOrNull(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) } - .takeIf { this.message.anyIsInstance() } - }?.message?.firstIsInstance() -} - -@JvmSynthetic -inline fun ContactMessage.nextMessageContainingOrNullAsync( - timeoutMillis: Long = -1, - coroutineContext: CoroutineContext = EmptyCoroutineContext -): Deferred { - return this.bot.async(coroutineContext) { - syncFromEventOrNull(timeoutMillis) { - takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) } - .takeIf { this.message.anyIsInstance() } - }?.message?.firstIsInstance() - } -} - - -@PlannedRemoval("1.0.0") -@Suppress("DEPRECATION") -@Deprecated(level = DeprecationLevel.HIDDEN, message = "for binary compatibility") -fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean { - return (this as ContactMessage).isContextIdenticalWith(another as ContactMessage) -} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessageEvent.kt similarity index 59% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessageEvent.kt index fa4c4879d..27d0a52d7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessageEvent.kt @@ -13,43 +13,33 @@ package net.mamoe.mirai.message import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Friend -import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.source import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.currentTimeSeconds -import net.mamoe.mirai.utils.getValue -import net.mamoe.mirai.utils.unsafeWeakRef /** - * 好友消息 + * 机器人收到的好友消息的事件 + * + * @see MessageEvent */ -class FriendMessage constructor( - sender: Friend, +class FriendMessageEvent constructor( + override val sender: Friend, override val message: MessageChain, override val time: Int -) : ContactMessage(), BroadcastControllable { - @PlannedRemoval("1.0.0") - @Deprecated("", level = DeprecationLevel.HIDDEN) - constructor(sender: QQ, message: MessageChain) : this(sender as Friend, message, currentTimeSeconds.toInt()) - - @PlannedRemoval("1.0.0") - @Deprecated("", level = DeprecationLevel.HIDDEN) - constructor(sender: Friend, message: MessageChain) : this(sender, message, currentTimeSeconds.toInt()) - +) : @PlannedRemoval("1.2.0") FriendMessage(), BroadcastControllable { init { - val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") + val source = + message.getOrNull(MessageSource) ?: throw IllegalArgumentException("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromFriend) { "source provided to a FriendMessage must be an instance of OnlineMessageSource.Incoming.FromFriend" } } - override val sender: Friend by sender.unsafeWeakRef() override val bot: Bot get() = sender.bot override val subject: Friend get() = sender override val senderName: String get() = sender.nick override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend - override fun toString(): String = "FriendMessage(sender=${sender.id}, message=$message)" + override fun toString(): String = "FriendMessageEvent(sender=${sender.id}, message=$message)" } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessageEvent.kt similarity index 66% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessageEvent.kt index c5dea5cf7..1721ada33 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessageEvent.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("DEPRECATION_ERROR", "unused", "NOTHING_TO_INLINE") + package net.mamoe.mirai.message import net.mamoe.mirai.Bot @@ -16,33 +18,28 @@ import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.Event import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.currentTimeSeconds -import net.mamoe.mirai.utils.getValue -import net.mamoe.mirai.utils.unsafeWeakRef -@Suppress("unused", "NOTHING_TO_INLINE") -class GroupMessage( +/** + * 机器人收到的群消息的事件 + * + * @see MessageEvent + */ +class GroupMessageEvent( override val senderName: String, /** * 发送方权限. */ val permission: MemberPermission, - sender: Member, + override val sender: Member, override val message: MessageChain, override val time: Int -) : ContactMessage(), Event { - @PlannedRemoval("1.0.0") - @Deprecated("", level = DeprecationLevel.HIDDEN) - constructor(senderName: String, permission: MemberPermission, sender: Member, message: MessageChain) : - this(senderName, permission, sender, message, currentTimeSeconds.toInt()) - +) : @PlannedRemoval("1.2.0") GroupMessage(), Event { init { val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessage must be an instance of OnlineMessageSource.Incoming.FromGroup" } } - override val sender: Member by sender.unsafeWeakRef() - val group: Group get() = sender.group + inline val group: Group get() = sender.group override val bot: Bot get() = sender.bot override val subject: Group get() = group @@ -52,5 +49,5 @@ class GroupMessage( inline fun At.asMember(): Member = group[this.target] override fun toString(): String = - "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" + "GroupMessageEvent(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageEvent.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageEvent.kt new file mode 100644 index 000000000..fc1591a1e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageEvent.kt @@ -0,0 +1,219 @@ +/* + * 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 + */ + +@file:Suppress( + "EXPERIMENTAL_UNSIGNED_LITERALS", + "EXPERIMENTAL_API_USAGE", + "unused", + "DECLARATION_CANT_BE_INLINED", "UNCHECKED_CAST", "NOTHING_TO_INLINE" +) + +@file:OptIn(MiraiInternalAPI::class) +@file:JvmMultifileClass +@file:JvmName("MessageEventKt") + +package net.mamoe.mirai.message + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.event.AbstractEvent +import net.mamoe.mirai.event.events.BotEvent +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.qqandroid.network.Packet +import net.mamoe.mirai.utils.* +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic + +/** + * 一个 (收到的) 消息事件. + * + * 它是一个 [BotEvent], 因此可以被 [监听][Bot.subscribe] + * + * 支持的消息类型: + * - [群消息事件][GroupMessageEvent] + * - [好友消息事件][FriendMessageEvent] + * - [临时会话消息事件][TempMessageEvent] + * + * @see isContextIdenticalWith 判断语境是否相同 + */ +@Suppress("DEPRECATION_ERROR") +abstract class MessageEvent : @PlannedRemoval("1.2.0") ContactMessage(), + BotEvent, MessageEventExtensions { + + /** + * 与这个消息事件相关的 [Bot] + */ + abstract override val bot: Bot + + /** + * 消息事件主体. + * + * - 对于好友消息, 这个属性为 [Friend] 的实例, 与 [sender] 引用相同; + * - 对于临时会话消息, 这个属性为 [Member] 的实例, 与 [sender] 引用相同; + * - 对于群消息, 这个属性为 [Group] 的实例, 与 [GroupMessageEvent.group] 引用相同 + * + * 在回复消息时, 可通过 [subject] 作为回复对象 + */ + abstract override val subject: Contact + + /** + * 发送人. + * + * 在好友消息时为 [Friend] 的实例, 在群消息时为 [Member] 的实例 + */ + abstract override val sender: User + + abstract val senderName: String + + /** 消息内容 */ + abstract override val message: MessageChain + + /** 消息发送时间 (由服务器提供) */ + abstract val time: Int + + /** 消息源 */ + open val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming +} + +/** 消息事件的扩展函数 */ +@Suppress("EXPOSED_SUPER_INTERFACE") // Functions are visible +interface MessageEventExtensions : + MessageEventPlatformExtensions { + + // region 发送 Message + + /** + * 给这个消息事件的主体发送消息 + * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 + * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 + */ + @JvmSynthetic + suspend inline fun reply(message: Message): MessageReceipt = + subject.sendMessage(message.asMessageChain()) as MessageReceipt + + @JvmSynthetic + suspend inline fun reply(plain: String): MessageReceipt = + subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt + + // endregion + + @JvmSynthetic + suspend inline fun ExternalImage.upload(): Image = this.upload(subject) + + @JvmSynthetic + suspend inline fun ExternalImage.send(): MessageReceipt = this.sendTo(subject) + + @JvmSynthetic + suspend inline fun Image.send(): MessageReceipt = this.sendTo(subject) + + @JvmSynthetic + suspend inline fun Message.send(): MessageReceipt = this.sendTo(subject) + + @JvmSynthetic + suspend inline fun String.send(): MessageReceipt = this.toMessage().sendTo(subject) + + // region 引用回复 + /** + * 给这个消息事件的主体发送引用回复消息 + * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 + * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 + */ + @JvmSynthetic + suspend inline fun quoteReply(message: MessageChain): MessageReceipt = + reply(this.message.quote() + message) + + @JvmSynthetic + suspend inline fun quoteReply(message: Message): MessageReceipt = reply(this.message.quote() + message) + + @JvmSynthetic + suspend inline fun quoteReply(plain: String): MessageReceipt = reply(this.message.quote() + plain) + + @JvmSynthetic + inline operator fun get(at: Message.Key): M { + return this.message[at] + } + + @JvmSynthetic + inline fun At.isBot(): Boolean = target == bot.id + + + /** + * 获取图片下载链接 + * @return "http://gchat.qpic.cn/gchatpic_new/..." + */ + @JvmSynthetic + suspend inline fun Image.url(): String = bot.queryImageUrl(this@url) +} + +/** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */ +internal expect interface MessageEventPlatformExtensions { + val subject: TSubject + val sender: TSender + val message: MessageChain + val bot: Bot +} + + +/** + * 已废弃, 请使用 [MessageEvent] + */ +@PlannedRemoval("1.2.0") +@Deprecated( + message = "use MessageEvent", + replaceWith = ReplaceWith("MessageEvent", "net.mamoe.mirai.message.MessageEvent"), + level = DeprecationLevel.ERROR +) +abstract class MessagePacketBase : Packet, BotEvent, AbstractEvent() + +@PlannedRemoval("1.2.0") +@Deprecated( + message = "Ambiguous name. Use MessageEvent instead", + replaceWith = ReplaceWith("MessageEvent", "net.mamoe.mirai.message.MessageEvent"), + level = DeprecationLevel.ERROR +) +@Suppress("DEPRECATION_ERROR") +abstract class MessagePacket : MessagePacketBase(), + BotEvent, MessageEventExtensions + +@PlannedRemoval("1.2.0") +@Deprecated( + message = "Ambiguous name. Use MessageEvent instead", + replaceWith = ReplaceWith("MessageEvent", "net.mamoe.mirai.message.MessageEvent"), + level = DeprecationLevel.ERROR +) +@Suppress("DEPRECATION_ERROR") +abstract class ContactMessage : MessagePacket(), + BotEvent, MessageEventExtensions + +@PlannedRemoval("1.2.0") +@Deprecated( + message = "Ambiguous name. Use FriendMessageEvent instead", + replaceWith = ReplaceWith("FriendMessageEvent", "net.mamoe.mirai.message.FriendMessageEvent"), + level = DeprecationLevel.ERROR +) +@Suppress("DEPRECATION_ERROR") +abstract class FriendMessage : MessageEvent() + +@PlannedRemoval("1.2.0") +@Deprecated( + message = "Ambiguous name. Use GroupMessageEvent instead", + replaceWith = ReplaceWith("GroupMessageEvent", "net.mamoe.mirai.message.GroupMessageEvent"), + level = DeprecationLevel.ERROR +) +@Suppress("DEPRECATION_ERROR") +abstract class GroupMessage : MessageEvent() + +@PlannedRemoval("1.2.0") +@Deprecated( + message = "Ambiguous name. Use TempMessageEvent instead", + replaceWith = ReplaceWith("TempMessageEvent", "net.mamoe.mirai.message.TempMessageEvent"), + level = DeprecationLevel.ERROR +) +abstract class TempMessage : MessageEvent() \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt index 78897648c..127d9917e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt @@ -19,7 +19,6 @@ import net.mamoe.mirai.message.data.* import net.mamoe.mirai.recallIn import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.internal.runBlocking import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext @@ -35,7 +34,7 @@ import kotlin.jvm.JvmSynthetic * @param target 消息发送对象 * * @see Group.sendMessage 发送群消息, 返回回执(此对象) - * @see QQ.sendMessage 发送群消息, 返回回执(此对象) + * @see User.sendMessage 发送群消息, 返回回执(此对象) * @see Member.sendMessage 发送临时消息, 返回回执(此对象) * * @see MessageReceipt.sourceId 源 id @@ -48,7 +47,7 @@ open class MessageReceipt( */ val source: OnlineMessageSource.Outgoing, /** - * 发送目标, 为 [Group] 或 [QQ] 或 [Member] + * 发送目标, 为 [Group] 或 [Friend] 或 [Member] */ val target: C, @@ -89,16 +88,6 @@ open class MessageReceipt( fun __quoteBlockingForJava__(): QuoteReply { return this.quote() } - - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - @JavaFriendlyAPI - @JvmName("recall") - fun __recallInBlockingForJava__2(timeMillis: Long): Job { - return recallIn(timeMillis = timeMillis) - } } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageType.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageType.kt deleted file mode 100644 index 1f4d7f660..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageType.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * 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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") - -package net.mamoe.mirai.message - - -@Suppress("unused") -enum class MessageType(val value: UByte) { - PLAIN_TEXT(0x01u), - AT(0x01u), // same as PLAIN - FACE(0x02u), - /** - * [ImageId.value] 长度为 42 的图片 - */ - IMAGE_42(0x03u), - /** - * [ImageId.value] 长度为 37 的图片 - */ - IMAGE_37(0x06u), - XML(0x19u) - ; - - - inline val intValue: Int get() = this.value.toInt() -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessageEvent.kt similarity index 69% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessage.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessageEvent.kt index f2603a80e..8455479e1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/TempMessageEvent.kt @@ -1,3 +1,5 @@ +@file:Suppress("DEPRECATION_ERROR", "unused", "NOTHING_TO_INLINE") + package net.mamoe.mirai.message import net.mamoe.mirai.Bot @@ -9,28 +11,22 @@ import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.source -import net.mamoe.mirai.utils.* /** - * 临时会话消息 + * 机器人收到的群临时会话消息的事件 + * + * @see MessageEvent */ -@SinceMirai("0.35.0") -class TempMessage( - sender: Member, +class TempMessageEvent( + override val sender: Member, override val message: MessageChain, override val time: Int -) : ContactMessage(), BroadcastControllable { - @PlannedRemoval("1.0.0") - @Deprecated("", level = DeprecationLevel.HIDDEN) - constructor(sender: Member, message: MessageChain) : - this(sender, message, currentTimeSeconds.toInt()) - +) : TempMessage(), BroadcastControllable { init { val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a TempMessage must be an instance of OnlineMessageSource.Incoming.FromTemp" } } - override val sender: Member by sender.unsafeWeakRef() override val bot: Bot get() = sender.bot override val subject: Member get() = sender inline val group: Group get() = sender.group @@ -38,5 +34,5 @@ class TempMessage( override val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp override fun toString(): String = - "TempMessage(sender=${sender.id} from group(${sender.group.id}), message=$message)" + "TempMessageEvent(sender=${sender.id} from group(${sender.group.id}), message=$message)" } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt index 0b8259c67..3736a1b46 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt @@ -17,7 +17,6 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.nameCardOrNick -import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic @@ -73,15 +72,6 @@ private constructor(val target: Long, val display: String) : return result } - - @OptIn(MiraiInternalAPI::class) - @Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_FUNCTION_RETURN_TYPE") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmName("followedBy") - @JvmSynthetic - override fun followedBy1(tail: Message): CombinedMessage { - return followedByInternalForBinaryCompatibility(tail) - } } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt index 745a8394f..cb5a1807f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.message.data -import net.mamoe.mirai.utils.SinceMirai import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -28,8 +27,6 @@ private const val displayA = "@全体成员" object AtAll : Message.Key, MessageContent { - - @SinceMirai("0.31.2") const val display = displayA override val typeName: String get() = "AtAll" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CustomMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CustomMessage.kt index 73318dddf..c18718c09 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CustomMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CustomMessage.kt @@ -33,7 +33,6 @@ import net.mamoe.mirai.utils.* * * @see CustomMessageMetadata 自定义消息元数据 */ -@SinceMirai("0.38.0") @MiraiExperimentalAPI sealed class CustomMessage : SingleMessage { /** @@ -181,7 +180,6 @@ sealed class CustomMessage : SingleMessage { * @see CustomMessage 查看更多信息 * @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素 */ -@SinceMirai("0.38.0") @MiraiExperimentalAPI abstract class CustomMessageMetadata : CustomMessage(), MessageMetadata { companion object Key : Message.Key { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/ForwardMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/ForwardMessage.kt index 623d91c39..b1ffc766a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/ForwardMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/ForwardMessage.kt @@ -12,11 +12,13 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.* -import net.mamoe.mirai.message.ContactMessage +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.User +import net.mamoe.mirai.contact.nameCardOrNick +import net.mamoe.mirai.message.MessageEvent import net.mamoe.mirai.message.data.ForwardMessage.DisplayStrategy import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic @@ -72,11 +74,10 @@ import kotlin.jvm.JvmSynthetic * * ### 构造 * - 使用 [DSL][buildForwardMessage] - * - 通过 [ContactMessage] 集合转换: [toForwardMessage] + * - 通过 [MessageEvent] 集合转换: [toForwardMessage] * * @see buildForwardMessage */ -@SinceMirai("0.39.0") class ForwardMessage @JvmOverloads constructor( /** * 消息列表 @@ -184,9 +185,8 @@ class ForwardMessage @JvmOverloads constructor( /** * 转换为 [ForwardMessage] */ -@SinceMirai("0.39.0") @JvmOverloads -fun Iterable.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage { +fun Iterable.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage { val iterator = this.iterator() if (!iterator.hasNext()) return ForwardMessage(emptyList(), displayStrategy) return ForwardMessage( @@ -205,7 +205,6 @@ fun Message.toForwardMessage( /** * 转换为 [ForwardMessage] */ -@SinceMirai("0.39.0") @JvmOverloads fun Message.toForwardMessage( senderId: Long, @@ -220,7 +219,6 @@ fun Message.toForwardMessage( * @see ForwardMessageBuilder 查看 DSL 帮助 * @see ForwardMessage 查看转发消息说明 */ -@SinceMirai("0.39.0") @JvmSynthetic inline fun buildForwardMessage( context: Contact, @@ -234,9 +232,8 @@ inline fun buildForwardMessage( * @see ForwardMessageBuilder 查看 DSL 帮助 * @see ForwardMessage 查看转发消息说明 */ -@SinceMirai("0.39.0") @JvmSynthetic -inline fun ContactMessage.buildForwardMessage( +inline fun MessageEvent.buildForwardMessage( context: Contact = this.subject, displayStrategy: DisplayStrategy = DisplayStrategy, block: ForwardMessageBuilder.() -> Unit @@ -247,7 +244,6 @@ inline fun ContactMessage.buildForwardMessage( /** * 标记转发消息 DSL */ -@SinceMirai("0.39.0") @Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) @DslMarker annotation class ForwardMessageDsl @@ -311,7 +307,6 @@ annotation class ForwardMessageDsl * * `S named "name1" named "name2" says M` 最终的发送人名称为 `"name2"` */ -@SinceMirai("0.39.0") class ForwardMessageBuilder private constructor( /** * 消息语境. 可为 [Group] 或 [User] diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt index 186990a94..a79a2b17a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/HummerMessage.kt @@ -17,7 +17,6 @@ import net.mamoe.mirai.message.data.PokeMessage.Types import net.mamoe.mirai.message.data.VipFace.Companion import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.SinceMirai import kotlin.jvm.* /** @@ -26,7 +25,6 @@ import kotlin.jvm.* * @see PokeMessage 戳一戳 * @see FlashImage 闪照 */ -@SinceMirai("0.31.0") sealed class HummerMessage : MessageContent { companion object Key : Message.Key { override val typeName: String @@ -44,7 +42,6 @@ sealed class HummerMessage : MessageContent { * * @see Types 使用伴生对象中的常量 */ -@SinceMirai("0.31.0") @OptIn(MiraiInternalAPI::class) data class PokeMessage internal constructor( /** @@ -158,7 +155,6 @@ data class PokeMessage internal constructor( * * @see Types 使用伴生对象中的常量 */ -@SinceMirai("0.39.5") @OptIn(MiraiInternalAPI::class) data class VipFace internal constructor( /** @@ -241,7 +237,6 @@ data class VipFace internal constructor( * * @see Image 查看图片相关信息 */ -@SinceMirai("0.33.0") sealed class FlashImage : MessageContent, HummerMessage() { companion object Key : Message.Key { /** @@ -289,22 +284,17 @@ sealed class FlashImage : MessageContent, HummerMessage() { override fun toString(): String = stringValue!! override fun contentToString(): String = "[闪照]" } - -@SinceMirai("0.33.0") inline fun Image.flash(): FlashImage = FlashImage(this) @JvmSynthetic -@SinceMirai("0.33.0") inline fun GroupImage.flash(): GroupFlashImage = FlashImage(this) as GroupFlashImage @JvmSynthetic -@SinceMirai("0.33.0") inline fun FriendImage.flash(): FriendFlashImage = FlashImage(this) as FriendFlashImage /** * @see FlashImage.invoke */ -@SinceMirai("0.33.0") data class GroupFlashImage(override val image: GroupImage) : FlashImage() { companion object Key : Message.Key { override val typeName: String @@ -315,7 +305,6 @@ data class GroupFlashImage(override val image: GroupImage) : FlashImage() { /** * @see FlashImage.invoke */ -@SinceMirai("0.33.0") data class FriendFlashImage(override val image: FriendImage) : FlashImage() { companion object Key : Message.Key { override val typeName: String diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt index 48a81f6b5..98cb178a6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt @@ -22,7 +22,6 @@ import net.mamoe.mirai.contact.Group import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.SinceMirai import kotlin.js.JsName import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -45,6 +44,10 @@ import kotlin.jvm.JvmSynthetic * @see Contact.sendImage 上传 [图片文件][ExternalImage] 并发送返回的 [Image] 作为一条消息 * @see Image.sendTo 上传图片并得到 [Image] 消息 * + * ### 下载图片 + * @see Image.queryUrl 扩展函数. 查询图片下载链接 + * @see Bot.queryImageUrl 查询图片下载链接 (Java 使用) + * * 查看平台 `actual` 定义以获取上传方式扩展. * * @see FlashImage 闪照 @@ -94,6 +97,7 @@ expect interface Image : Message, MessageContent { * @property imageId 形如 `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (后缀一定为 `".mirai"`) * @see Image 查看更多说明 */ +@PlannedRemoval("1.2.0") // make internal @Suppress("DEPRECATION_ERROR") // CustomFace @OptIn(MiraiInternalAPI::class) @@ -109,7 +113,6 @@ sealed class GroupImage : AbstractImage() { * 在 Java 使用: `MessageUtils.calculateImageMd5(image)` */ @get:JvmName("calculateImageMd5") -@SinceMirai("0.39.0") val Image.md5: ByteArray get() = calculateImageMd5ByImageId(imageId) @@ -119,6 +122,7 @@ val Image.md5: ByteArray * * [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度) 或 `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度) */ // NotOnlineImage +@PlannedRemoval("1.2.0") // make internal @Suppress("DEPRECATION_ERROR") @OptIn(MiraiInternalAPI::class) sealed class FriendImage : AbstractImage() { @@ -133,7 +137,6 @@ sealed class FriendImage : AbstractImage() { * `/f8f1ab55-bf8e-4236-b55e-955848d7069f` * @see FRIEND_IMAGE_ID_REGEX_2 */ -@SinceMirai("0.39.2") // Java: MessageUtils.FRIEND_IMAGE_ID_REGEX_1 val FRIEND_IMAGE_ID_REGEX_1 = Regex("""/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}""") @@ -143,7 +146,6 @@ val FRIEND_IMAGE_ID_REGEX_1 = Regex("""/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a * `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` * @see FRIEND_IMAGE_ID_REGEX_1 */ -@SinceMirai("0.39.2") // Java: MessageUtils.FRIEND_IMAGE_ID_REGEX_2 val FRIEND_IMAGE_ID_REGEX_2 = Regex("""/[0-9]*-[0-9]*-[0-9a-fA-F]{32}""") @@ -153,21 +155,9 @@ val FRIEND_IMAGE_ID_REGEX_2 = Regex("""/[0-9]*-[0-9]*-[0-9a-fA-F]{32}""") * `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` */ @Suppress("RegExpRedundantEscape") // This is required on Android -@SinceMirai("0.39.2") // Java: MessageUtils.GROUP_IMAGE_ID_REGEX val GROUP_IMAGE_ID_REGEX = Regex("""\{[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}\.mirai""") -/** - * 在 `0.39.0` 前的图片的正则表示 - */ -@Suppress("RegExpRedundantEscape") // This is required on Android -@Deprecated("Only for temporal use", - replaceWith = ReplaceWith("GROUP_IMAGE_ID_REGEX", "net.mamoe.mirai.message.data.GROUP_IMAGE_ID_REGEX")) -@SinceMirai("0.39.2") -@PlannedRemoval("1.0.0") -// Java: MessageUtils.GROUP_IMAGE_ID_REGEX_OLD -val GROUP_IMAGE_ID_REGEX_OLD = Regex("""\{[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}\..*""") - /** * 通过 [Image.imageId] 构造一个 [Image] 以便发送. * 这个图片必须是服务器已经存在的图片. @@ -185,7 +175,6 @@ fun Image(imageId: String): OfflineImage = when { imageId matches FRIEND_IMAGE_ID_REGEX_1 -> OfflineFriendImage(imageId) imageId matches FRIEND_IMAGE_ID_REGEX_2 -> OfflineFriendImage(imageId) imageId matches GROUP_IMAGE_ID_REGEX -> OfflineGroupImage(imageId) - imageId matches GROUP_IMAGE_ID_REGEX_OLD -> OfflineGroupImage(imageId) else -> throw IllegalArgumentException("Illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE") } @@ -279,7 +268,7 @@ data class OfflineGroupImage( ) : GroupImage(), OfflineImage { init { @Suppress("DEPRECATION") - require(imageId matches GROUP_IMAGE_ID_REGEX || imageId matches GROUP_IMAGE_ID_REGEX_OLD) { + require(imageId matches GROUP_IMAGE_ID_REGEX) { "Illegal imageId. It must matches GROUP_IMAGE_ID_REGEX" } } @@ -329,18 +318,10 @@ abstract class OnlineFriendImage : FriendImage(), OnlineImage // endregion -@PlannedRemoval("1.0.0") -@JvmSynthetic -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -@Suppress("FunctionName") -@JsName("newImage") -@JvmName("newImage") -fun Image2(imageId: String): Image = Image(imageId) - - /** * 所有 [Image] 实现的基类. */ +@PlannedRemoval("1.2.0") // make internal @Deprecated( "This is internal API. Use Image instead", level = DeprecationLevel.HIDDEN, // so that others can't see this class diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt index dffe346e7..94e41e1da 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.message.MessageEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message.Key import net.mamoe.mirai.utils.MiraiInternalAPI @@ -26,8 +27,6 @@ import kotlin.jvm.JvmSynthetic /** * 可发送的或从服务器接收的消息. * - * 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] 等. - * * [消息][Message] 分为 * - [SingleMessage]: * - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource], [引用回复][QuoteReply]. @@ -63,13 +62,18 @@ import kotlin.jvm.JvmSynthetic * * 即, `appendable.append(message)` 相当于 `appendable.append(message.toString())` * + * #### 发送消息 + * - 通过 [Contact] 中的成员函数: [Contact.sendMessage] + * - 通过 [Message] 的扩展函数: [Message.sendTo] + * - 在 [MessageEvent] 中使用 [MessageEvent.reply] 等捷径 + * * @see PlainText 纯文本 * @see Image 图片 * @see Face 原生表情 * @see At 一个群成员的引用 * @see AtAll 全体成员的引用 * @see QuoteReply 一条消息的引用 - * @see RichMessage 富文本消息, 如 [Xml][XmlMessage], [小程序][LightApp], [Json][JsonMessage] + * @see RichMessage 富文本消息, 如 [XML 和 JSON][ServiceMessage], [小程序][LightApp] * @see HummerMessage 一些特殊的消息, 如 [闪照][FlashImage], [戳一戳][PokeMessage] * @see CustomMessage 自定义消息类型 * @@ -95,7 +99,6 @@ interface Message { // must be interface. Don't consider any changes. /** * 此 [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName`, 如 "QuoteReply", "PlainText" */ - @SinceMirai("0.34.0") val typeName: String } @@ -118,7 +121,6 @@ interface Message { // must be interface. Don't consider any changes. * * @see plus `+` 操作符重载 */ - @SinceMirai("0.34.0") @JvmSynthetic // in java they should use `plus` instead fun followedBy(tail: Message): MessageChain = followedByImpl(tail) @@ -151,7 +153,6 @@ interface Message { // must be interface. Don't consider any changes. * * @see toString 得到包含 mirai 消息元素代码的, 易读的字符串 */ - @SinceMirai("0.34.0") fun contentToString(): String @@ -164,7 +165,6 @@ interface Message { // must be interface. Don't consider any changes. * * @sample net.mamoe.mirai.message.data.ContentEqualsTest */ - @SinceMirai("0.38.0") fun contentEquals(another: Message, ignoreCase: Boolean = false): Boolean = contentEqualsImpl(another, ignoreCase) /** @@ -176,7 +176,6 @@ interface Message { // must be interface. Don't consider any changes. * * @sample net.mamoe.mirai.message.data.ContentEqualsTest */ - @SinceMirai("0.38.0") fun contentEquals(another: String, ignoreCase: Boolean = false): Boolean { if (!this.contentToString().equals(another, ignoreCase = ignoreCase)) return false return when (this) { @@ -195,79 +194,15 @@ interface Message { // must be interface. Don't consider any changes. // `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)` operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage()) - - ////////////////////////////////////// - // FOR BINARY COMPATIBILITY UNTIL 1.0.0 - ////////////////////////////////////// - - @PlannedRemoval("1.0.0") - @JvmSynthetic - @Deprecated( - "有歧义, 自行使用 contentToString() 比较", - ReplaceWith("this.contentToString() == other"), - DeprecationLevel.ERROR - ) - infix fun eq(other: Message): Boolean = this.contentToString() == other.contentToString() - - /** - * 将 [contentToString] 与 [other] 比较 - */ - @PlannedRemoval("1.0.0") - @JvmSynthetic - @Deprecated( - "有歧义, 自行使用 contentToString() 比较", - ReplaceWith("this.contentToString() == other"), - DeprecationLevel.ERROR - ) - infix fun eq(other: String): Boolean = this.contentToString() == other - - @PlannedRemoval("1.0.0") - @JvmSynthetic - @Deprecated( - "有歧义, 自行使用 contentToString() 比较", - ReplaceWith("this.contentToString() == other"), - DeprecationLevel.ERROR - ) - operator fun contains(sub: String): Boolean = false - - @PlannedRemoval("1.0.0") - @Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_FUNCTION_RETURN_TYPE") - @JvmName("followedBy") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - fun followedBy1(tail: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(tail) - - @PlannedRemoval("1.0.0") - @Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_FUNCTION_RETURN_TYPE") - @JvmName("plus") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - fun plus1(another: Message): CombinedMessage = this.followedByInternalForBinaryCompatibility(another) - - @PlannedRemoval("1.0.0") - @Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_FUNCTION_RETURN_TYPE") - @JvmName("plus") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - fun plus1(another: SingleMessage): CombinedMessage = this.followedByInternalForBinaryCompatibility(another) - - @PlannedRemoval("1.0.0") - @Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_FUNCTION_RETURN_TYPE") - @JvmName("plus") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - fun plus1(another: String): CombinedMessage = this.followedByInternalForBinaryCompatibility(another.toMessage()) - - @PlannedRemoval("1.0.0") - @Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_FUNCTION_RETURN_TYPE") - @JvmName("plus") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - fun plus1(another: CharSequence): CombinedMessage = - this.followedByInternalForBinaryCompatibility(another.toString().toMessage()) } +/** + * [Message.contentToString] 的捷径 + */ +inline val Message.content: String get() = contentToString() + + /** * 判断消息内容是否为空. * @@ -277,15 +212,12 @@ interface Message { // must be interface. Don't consider any changes. * - [PlainText] 长度为 0 * - [MessageChain] 所有元素都满足 [isContentEmpty] */ -@SinceMirai("0.39.3") fun Message.isContentEmpty(): Boolean = when (this) { is MessageMetadata -> true is PlainText -> this.content.isEmpty() is MessageChain -> this.all { it.isContentEmpty() } else -> false } - -@SinceMirai("0.39.3") inline fun Message.isContentNotEmpty(): Boolean = !this.isContentEmpty() inline fun Message.isPlain(): Boolean = this is PlainText @@ -314,47 +246,19 @@ inline operator fun Message.times(count: Int): MessageChain = this.repeat(count) @Suppress("OverridingDeprecatedMember") interface SingleMessage : Message { - @PlannedRemoval("1.0.0") - @JvmSynthetic - @Deprecated( - "有歧义, 自行使用 contentToString() 比较", - ReplaceWith("this.contentToString() == other"), - DeprecationLevel.ERROR - ) - /* final */ override operator fun contains(sub: String): Boolean = sub in this.contentToString() - - @PlannedRemoval("1.0.0") - @JvmSynthetic - @Deprecated( - "有歧义, 自行使用 contentToString() 比较", - ReplaceWith("this.contentToString() == other"), - DeprecationLevel.ERROR - ) - /* final */ override infix fun eq(other: Message): Boolean = this.contentToString() == other.contentToString() - - @PlannedRemoval("1.0.0") - @JvmSynthetic - @Deprecated( - "有歧义, 自行使用 contentToString() 比较", - ReplaceWith("this.contentToString() == other"), - DeprecationLevel.ERROR - ) - /* final */ override infix fun eq(other: String): Boolean = this.contentToString() == other - - - @PlannedRemoval("1.1.0") + @PlannedRemoval("1.2.0") @JvmSynthetic @SinceMirai("1.0.0") @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) fun length(): Int = this.toString().length - @PlannedRemoval("1.1.0") + @PlannedRemoval("1.2.0") @JvmSynthetic @SinceMirai("1.0.0") @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) fun charAt(index: Int): Char = this.toString()[index] - @PlannedRemoval("1.1.0") + @PlannedRemoval("1.2.0") @JvmSynthetic @SinceMirai("1.0.0") @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @@ -379,7 +283,6 @@ interface MessageMetadata : SingleMessage * * 实现此接口的元素将会在连接时自动处理替换. */ -@SinceMirai("0.34.0") interface ConstrainSingle : MessageMetadata { val key: Key } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index 2cb94afaa..c92f3868b 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -10,13 +10,13 @@ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("unused", "NOTHING_TO_INLINE") +@file:OptIn(MiraiInternalAPI::class) package net.mamoe.mirai.message.data +import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.SinceMirai import kotlin.js.JsName import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -32,7 +32,7 @@ import kotlin.reflect.KProperty * @see asMessageChain 将单个 [Message] 转换为 [MessageChain] * @see asMessageChain 将 [Iterable] 或 [Sequence] 委托为 [MessageChain] * - * @see foreachContent 遍历内容 + * @see forEachContent 遍历内容 * * @see orNull 属性委托扩展 * @see orElse 属性委托扩展 @@ -40,18 +40,9 @@ import kotlin.reflect.KProperty * @see flatten 扁平化 */ interface MessageChain : Message, Iterable { - @PlannedRemoval("1.0.0") - @Deprecated( - "有歧义, 自行使用 contentToString() 比较", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("this.contentToString().contains(sub)") - ) - /* final */ override operator fun contains(sub: String): Boolean = this.contentToString().contains(sub) - /** * 元素数量. [EmptyMessageChain] 不参加计数. */ - @SinceMirai("0.31.1") val size: Int /** @@ -60,56 +51,42 @@ interface MessageChain : Message, Iterable { * @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key] * @throws NoSuchElementException 当找不到这个类型的 [Message] 时 */ - @Suppress("INAPPLICABLE_JVM_NAME") + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "INAPPLICABLE_JVM_NAME") @JvmName("first") - /* final */ operator fun get(key: Message.Key): M = first(key) + final operator fun get(key: Message.Key): M = first(key) /** * 获取第一个类型为 [key] 的 [Message] 实例, 找不到则返回 `null` * * @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key] */ - @Suppress("INAPPLICABLE_JVM_NAME") + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "INAPPLICABLE_JVM_NAME") @JvmName("firstOrNull") - /* final */ fun getOrNull(key: Message.Key): M? = firstOrNull(key) + final fun getOrNull(key: Message.Key): M? = firstOrNull(key) /** - * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]. + * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face] 等 * 仅供 `Java` 使用 */ - @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "FunctionName", "INAPPLICABLE_JVM_NAME") @JsName("forEachContent") @JvmName("forEachContent") - @MiraiInternalAPI - fun `__forEachContent for Java__`(block: (Message) -> Unit) { + @JavaFriendlyAPI + final fun __forEachContentForJava__(block: (Message) -> Unit) { this.forEachContent(block) } /** - * 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]. + * 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [QuoteReply] 等 * 仅供 `Java` 使用 */ - @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") + @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "FunctionName", "INAPPLICABLE_JVM_NAME") @JsName("forEach") @JvmName("forEach") - @MiraiInternalAPI - fun `__forEach for Java__`(block: (Message) -> Unit) { + @JavaFriendlyAPI + final fun __forEachForJava__(block: (Message) -> Unit) { this.forEach(block) } - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") - @JvmName("get") - fun get2(key: Message.Key): M = first(key) - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) - @JvmSynthetic - @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") - @JvmName("getOrNull") - fun getOrNull2(key: Message.Key): M? = getOrNull(key) } // region accessors @@ -117,7 +94,6 @@ interface MessageChain : Message, Iterable { /** * 遍历每一个 [消息内容][MessageContent] */ -@SinceMirai("0.39.0") @JvmSynthetic inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) { for (element in this) { @@ -128,12 +104,6 @@ inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) { } } -@Deprecated("typo, use forEachContent", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("forEachContent(block)")) -@JvmSynthetic -inline fun MessageChain.foreachContent(block: (MessageContent) -> Unit) = forEachContent(block) - /** * 如果每一个 [消息内容][MessageContent] 都满足 [block], 返回 `true` */ @@ -435,18 +405,4 @@ object EmptyMessageChain : MessageChain, Iterator { override fun iterator(): Iterator = this override fun hasNext(): Boolean = false override fun next(): SingleMessage = throw NoSuchElementException("EmptyMessageChain is empty.") -} - -/** - * Null 的 [MessageChain]. - * 它不包含任何元素, 也没有创建任何 list. - */ -@PlannedRemoval("1.0.0") -@Deprecated("ambiguous. use `null` or EmptyMessageChain instead", level = DeprecationLevel.ERROR) -object NullMessageChain : MessageChain { - override fun toString(): String = "NullMessageChain" - override fun contentToString(): String = "" - override val size: Int get() = 0 - override fun equals(other: Any?): Boolean = other === this - override fun iterator(): MutableIterator = error("accessing NullMessageChain") -} +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChainBuilder.kt similarity index 99% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChainBuilder.kt index 79e0c2777..494d81dd5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChainBuilder.kt @@ -14,7 +14,6 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.SinceMirai import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic @@ -159,7 +158,6 @@ open class MessageChainBuilder private constructor( /** * 将所有已有元素引用复制到一个新的 [MessageChainBuilder] */ - @SinceMirai("0.38.0") fun copy(): MessageChainBuilder { return MessageChainBuilder(container.toMutableList()) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt index d45835e2c..1ae2b40df 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt @@ -16,10 +16,12 @@ package net.mamoe.mirai.message.data import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* -import net.mamoe.mirai.message.ContactMessage +import net.mamoe.mirai.message.MessageEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.recallIn -import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.LazyProperty +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmMultifileClass @@ -57,7 +59,6 @@ import kotlin.jvm.JvmSynthetic * @see OfflineMessageSource 离线消息的 [MessageSource] */ @OptIn(MiraiExperimentalAPI::class) -@SinceMirai("0.33.0") sealed class MessageSource : Message, MessageMetadata, ConstrainSingle { companion object Key : Message.Key { override val typeName: String get() = "MessageSource" @@ -93,7 +94,6 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle { @@ -279,34 +278,7 @@ sealed class OnlineMessageSource : MessageSource() { final override val target: Group get() = group inline val group: Group get() = sender.group } - - - ////////////////////////////////// - //// FOR BINARY COMPATIBILITY //// - ////////////////////////////////// - - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility until 1.0.0", level = DeprecationLevel.HIDDEN) - @get:JvmName("target") - @get:JvmSynthetic - final override val target2: Any - get() = target } - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility until 1.0.0", level = DeprecationLevel.HIDDEN) - @get:JvmName("target") - @get:JvmSynthetic - open val target2: Any - get() = target - - @PlannedRemoval("1.0.0") - @Deprecated("for binary compatibility until 1.0.0", level = DeprecationLevel.HIDDEN) - @get:JvmName("sender") - @get:JvmSynthetic - open val sender2: Any - get() = sender } /** @@ -315,7 +287,6 @@ sealed class OnlineMessageSource : MessageSource() { * * @see buildMessageSource 构建一个 [OfflineMessageSource] */ -@SinceMirai("0.33.0") abstract class OfflineMessageSource : MessageSource() { companion object Key : Message.Key { override val typeName: String @@ -325,8 +296,6 @@ abstract class OfflineMessageSource : MessageSource() { enum class Kind { GROUP, FRIEND, - - @SinceMirai("0.36.0") TEMP } @@ -380,7 +349,7 @@ fun MessageSource.quote(): QuoteReply { } /** - * 引用这条消息. 仅从服务器接收的消息 (即来自 [ContactMessage]) 才可以通过这个方式被引用. + * 引用这条消息. 仅从服务器接收的消息 (即来自 [MessageEvent]) 才可以通过这个方式被引用. * @see QuoteReply */ fun MessageChain.quote(): QuoteReply { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/OfflineMessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSourceBuilder.kt similarity index 98% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/OfflineMessageSource.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSourceBuilder.kt index 2d9e25fca..bad6d9210 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/OfflineMessageSource.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSourceBuilder.kt @@ -19,7 +19,6 @@ import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.SinceMirai import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -28,7 +27,6 @@ import kotlin.jvm.JvmSynthetic /** * 将在线消息源转换为离线消息源. */ -@SinceMirai("0.39.0") @JvmName("toOfflineMessageSource") fun OnlineMessageSource.toOffline(): OfflineMessageSource = OfflineMessageSourceByOnline(this) @@ -44,7 +42,6 @@ fun OnlineMessageSource.toOffline(): OfflineMessageSource = * @see buildMessageSource 查看更多说明 */ @MiraiExperimentalAPI -@SinceMirai("0.39.0") @JvmName("copySource") fun MessageSource.copyAmend( block: MessageSourceAmender.() -> Unit @@ -53,7 +50,6 @@ fun MessageSource.copyAmend( /** * 仅于 [copyAmend] 中修改 [MessageSource] */ -@SinceMirai("0.39.0") interface MessageSourceAmender { var kind: OfflineMessageSource.Kind var fromUin: Long @@ -65,7 +61,6 @@ interface MessageSourceAmender { var originalMessage: MessageChain /** 从另一个 [MessageSource] 中复制 [id], [internalId], [time]*/ - @SinceMirai("0.39.2") fun metadataFrom(another: MessageSource) { this.id = another.id this.internalId = another.internalId @@ -107,7 +102,6 @@ interface MessageSourceAmender { * } * ``` */ -@SinceMirai("0.39.0") @JvmSynthetic @MiraiExperimentalAPI fun Bot.buildMessageSource(block: MessageSourceBuilder.() -> Unit): MessageSource { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt index 2687de23d..a3bea897e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt @@ -29,7 +29,7 @@ data class PlainText( val content: String ) : MessageContent { - @PlannedRemoval("1.1.0") + @PlannedRemoval("1.2.0") @Deprecated( "use content instead for clearer semantics", level = DeprecationLevel.WARNING, diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt index ed2e6c084..ae3e6147f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt @@ -16,7 +16,6 @@ package net.mamoe.mirai.message.data import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.SinceMirai import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmMultifileClass @@ -43,7 +42,6 @@ import kotlin.jvm.JvmSynthetic * @see MessageSource 获取有关消息源的更多信息 */ @OptIn(MiraiExperimentalAPI::class) -@SinceMirai("0.33.0") class QuoteReply(val source: MessageSource) : Message, MessageMetadata, ConstrainSingle { companion object Key : Message.Key { override val typeName: String @@ -68,7 +66,6 @@ inline val QuoteReply.id: Int /** * @see MessageSource.internalId */ -@SinceMirai("0.39.2") @get:JvmSynthetic inline val QuoteReply.internalId: Int get() = source.internalId diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt index 7a9188cd3..387518695 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt @@ -14,8 +14,6 @@ package net.mamoe.mirai.message.data import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.SinceMirai import kotlin.annotation.AnnotationTarget.* import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -31,7 +29,6 @@ import kotlin.jvm.JvmSynthetic * @see LightApp 小程序 (JSON) */ // not using sealed class for customized implementations -@SinceMirai("0.27.0") interface RichMessage : MessageContent { /** @@ -50,14 +47,12 @@ interface RichMessage : MessageContent { * @suppress 此 API 不稳定, 可能在任意时刻被删除 */ @MiraiExperimentalAPI - @SinceMirai("0.30.0") companion object Templates : Message.Key { /** * @suppress 此 API 不稳定, 可能在任意时刻被删除 */ @MiraiExperimentalAPI - @SinceMirai("0.30.0") fun share( url: String, title: String? = null, @@ -84,20 +79,6 @@ interface RichMessage : MessageContent { } } - @PlannedRemoval("1.0.0") - @JvmName("share") - @Deprecated( - "for binary compatibility", level = DeprecationLevel.HIDDEN - ) - @Suppress("DEPRECATION_ERROR") - @MiraiExperimentalAPI - fun shareDeprecated( - url: String, - title: String? = null, - content: String? = null, - coverUrl: String? = null - ): XmlMessage = share(url, title, content, coverUrl) as XmlMessage - override val typeName: String get() = "RichMessage" } @@ -112,7 +93,6 @@ interface RichMessage : MessageContent { * * @see ServiceMessage 服务消息 */ -@SinceMirai("0.27.0") data class LightApp(override val content: String) : RichMessage { companion object Key : Message.Key { override val typeName: String get() = "LightApp" @@ -131,7 +111,6 @@ data class LightApp(override val content: String) : RichMessage { * * @see LightApp 小程序类型消息 */ -@SinceMirai("0.37.3") open class ServiceMessage(val serviceId: Int, final override val content: String) : RichMessage { companion object Key : Message.Key { override val typeName: String get() = "ServiceMessage" @@ -154,59 +133,6 @@ open class ServiceMessage(val serviceId: Int, final override val content: String } -/** - * Json 消息. - * - * 由于 [serviceId] 不准确, 请使用 [ServiceMessage] 并手动指定 `serviceId` - * - * @see LightApp 一些 json 消息实际上是 [LightApp] - */ -@PlannedRemoval("1.0.0") -@Deprecated("use ServiceMessage with serviceId 1", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("ServiceMessage")) -@Suppress("DEPRECATION_ERROR") -@MiraiExperimentalAPI -class JsonMessage -@Deprecated("use ServiceMessage with serviceId 1", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("ServiceMessage(1, content)")) -constructor(content: String) : ServiceMessage(1, content) { - @Suppress("DEPRECATION") - companion object Key : Message.Key { - override val typeName: String get() = "JsonMessage" - } -} - - -/** - * XML 消息, 如分享, 卡片等. - * - * 由于 [serviceId] 不准确, 请使用 [ServiceMessage] 并手动指定 `serviceId` - * - * @param serviceId 目前未知, 一般为 60 - * - * @see buildXmlMessage 使用 DSL 构造一个 XML 消息 - */ -@PlannedRemoval("1.0.0") -@Deprecated("use ServiceMessage with serviceId 1", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("ServiceMessage")) -@MiraiExperimentalAPI -@Suppress("DEPRECATION_ERROR") -@SinceMirai("0.27.0") -class XmlMessage @MiraiExperimentalAPI("Maybe replaced with an enum") -constructor(serviceId: Int = 60, content: String) : ServiceMessage(serviceId, content) { - - @MiraiExperimentalAPI - @Deprecated("specify serviceId explicitly", replaceWith = ReplaceWith("XmlMessage(60, content)")) - constructor(content: String) : this(60, content) - - companion object Key : Message.Key { - override val typeName: String get() = "XmlMessage" - } -} - /* commonElem=CommonElem#750141174 { businessType=0x00000001(1) @@ -221,10 +147,9 @@ commonElem=CommonElem#750141174 { */ @Suppress("DEPRECATION_ERROR") @JvmSynthetic -@SinceMirai("0.27.0") @MiraiExperimentalAPI inline fun buildXmlMessage(serviceId: Int, block: @XmlMessageDsl XmlMessageBuilder.() -> Unit): ServiceMessage = - XmlMessage(serviceId, XmlMessageBuilder().apply(block).text) + ServiceMessage(serviceId, XmlMessageBuilder().apply(block).text) @MiraiExperimentalAPI @Target(CLASS, FUNCTION, TYPE) @@ -273,8 +198,6 @@ class XmlMessageBuilder( sourceName = name sourceIconURL = iconURL } - - @SinceMirai("0.27.0") @XmlMessageDsl class ItemBuilder @PublishedApi internal constructor( var bg: Int = 0, @@ -297,17 +220,6 @@ class XmlMessageBuilder( } } } - - -@JvmSynthetic -@SinceMirai("0.27.0") -@MiraiExperimentalAPI -@Deprecated("specify serviceId explicitly", ReplaceWith("buildXmlMessage(60, block)")) -inline fun buildXmlMessage(block: @XmlMessageDsl XmlMessageBuilder.() -> Unit): ServiceMessage = - buildXmlMessage(60, block) - - -@SinceMirai("0.31.0") @MiraiExperimentalAPI internal class LongMessage internal constructor(content: String, val resId: String) : ServiceMessage(35, content) { companion object Key : Message.Key { @@ -316,5 +228,4 @@ internal class LongMessage internal constructor(content: String, val resId: Stri } @OptIn(MiraiExperimentalAPI::class) -@SinceMirai("0.39.0") internal class ForwardMessageInternal(content: String) : ServiceMessage(35, content) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/_HummerMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/_HummerMessage.kt deleted file mode 100644 index 88dcc2889..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/_HummerMessage.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 - */ - -@file:JvmName("HummerMessageKt") -@file:Suppress("NOTHING_TO_INLINE") - -package net.mamoe.mirai.message.data - -import net.mamoe.mirai.utils.PlannedRemoval -import net.mamoe.mirai.utils.SinceMirai -import kotlin.jvm.JvmName -import kotlin.jvm.JvmSynthetic - - -/* -因为文件改名为做的兼容 - */ - -@PlannedRemoval("1.0.0") -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -@JvmName("flash") -@SinceMirai("0.33.0") -inline fun Image.flash2(): FlashImage = FlashImage(this) - -@PlannedRemoval("1.0.0") -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -@JvmName("flash") -@JvmSynthetic -@SinceMirai("0.33.0") -inline fun GroupImage.flash2(): GroupFlashImage = FlashImage(this) as GroupFlashImage - -@PlannedRemoval("1.0.0") -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -@JvmName("flash") -@JvmSynthetic -@SinceMirai("0.33.0") -inline fun FriendImage.flash2(): FriendFlashImage = FlashImage(this) as FriendFlashImage diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/_Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/_Message.kt deleted file mode 100644 index 364b34e22..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/_Message.kt +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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 - */ - -@file:JvmName("MessageKt") -@file:Suppress("NOTHING_TO_INLINE") - -package net.mamoe.mirai.message.data - -import net.mamoe.mirai.utils.PlannedRemoval -import kotlin.jvm.JvmName - - -/* -因为文件改名为做的兼容 - */ - -@PlannedRemoval("1.0.0") -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -@JvmName("isPlain") -inline fun Message.isPlain2(): Boolean = this is PlainText - -@PlannedRemoval("1.0.0") -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -@JvmName("isNotPlain") -inline fun Message.isNotPlain2(): Boolean = this !is PlainText - -@PlannedRemoval("1.0.0") -@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) -@JvmName("repeat") -// inline: for future removal -inline fun Message.repeat2(count: Int): MessageChain { - if (this is ConstrainSingle<*>) { - // fast-path - return this.asMessageChain() - } - return buildMessageChain(count) { - add(this@repeat2) - } -} diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt index f3078c66e..5377169b6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/impl.kt @@ -37,13 +37,6 @@ private fun Message.hasDuplicationOfConstrain(key: Message.Key<*>): Boolean { } } -@OptIn(MiraiInternalAPI::class) -@JvmSynthetic -@Suppress("DEPRECATION_ERROR") -internal fun Message.followedByInternalForBinaryCompatibility(tail: Message): CombinedMessage { - return CombinedMessage(EmptyMessageChain, this.followedBy(tail)) -} - @JvmSynthetic internal fun Message.contentEqualsImpl(another: Message, ignoreCase: Boolean): Boolean { if (!this.contentToString().equals(another.contentToString(), ignoreCase = ignoreCase)) return false @@ -220,9 +213,7 @@ internal fun MessageChain.firstOrNullImpl(key: Message.Key): M? OnlineMessageSource.Incoming.FromGroup -> firstIsInstanceOrNull() OnlineMessageSource.Incoming.FromFriend -> firstIsInstanceOrNull() OnlineMessageSource -> firstIsInstanceOrNull() - XmlMessage -> firstIsInstanceOrNull() LongMessage -> firstIsInstanceOrNull() - JsonMessage -> firstIsInstanceOrNull() RichMessage -> firstIsInstanceOrNull() LightApp -> firstIsInstanceOrNull() PokeMessage -> firstIsInstanceOrNull() @@ -366,7 +357,6 @@ internal fun calculateImageMd5ByImageId(imageId: String): ByteArray { imageId matches FRIEND_IMAGE_ID_REGEX_2 -> imageId.imageIdToMd5(imageId.skipToSecondHyphen() + 1) imageId matches FRIEND_IMAGE_ID_REGEX_1 -> imageId.imageIdToMd5(1) imageId matches GROUP_IMAGE_ID_REGEX -> imageId.imageIdToMd5(1) - imageId matches GROUP_IMAGE_ID_REGEX_OLD -> imageId.imageIdToMd5(1) else -> error( "illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/utils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/utils.kt new file mode 100644 index 000000000..2ecf885a6 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/utils.kt @@ -0,0 +1,117 @@ +/* + * 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 + */ + +@file:JvmMultifileClass +@file:JvmName("MessageEventKt") +@file:Suppress("unused") + +package net.mamoe.mirai.message + +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import net.mamoe.mirai.event.syncFromEvent +import net.mamoe.mirai.event.syncFromEventOrNull +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.utils.PlannedRemoval +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic + + +/** + * 判断两个 [MessageEvent] 的 [MessageEvent.sender] 和 [MessageEvent.subject] 是否相同 + */ +fun MessageEvent.isContextIdenticalWith(another: MessageEvent): Boolean { + return this.sender == another.sender && this.subject == another.subject +} + + +/** + * 挂起当前协程, 等待下一条 [MessageEvent.sender] 和 [MessageEvent.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessageEvent] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 + * + * @see syncFromEvent 实现原理 + */ +@JvmSynthetic +suspend inline fun P.nextMessage( + timeoutMillis: Long = -1, + noinline filter: suspend P.(P) -> Boolean = { true } +): MessageChain { + return syncFromEvent(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) } + }.message +} + +/** + * 挂起当前协程, 等待下一条 [MessageEvent.sender] 和 [MessageEvent.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessageEvent] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 + * @return 消息链. 超时时返回 `null` + * + * @see syncFromEventOrNull 实现原理 + */ +@JvmSynthetic +suspend inline fun P.nextMessageOrNull( + timeoutMillis: Long, + noinline filter: suspend P.(P) -> Boolean = { true } +): MessageChain? { + require(timeoutMillis > 0) { "timeoutMillis must be > 0" } + return syncFromEventOrNull(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) } + }?.message +} + +/** + * @see nextMessage + */ +@JvmSynthetic +inline fun P.nextMessageAsync( + timeoutMillis: Long = -1, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline filter: suspend P.(P) -> Boolean = { true } +): Deferred { + return this.bot.async(coroutineContext) { + nextMessage(timeoutMillis, filter) + } +} + +/** + * [nextMessageOrNull] 的异步版本 + * + * @see nextMessageOrNull + */ +@JvmSynthetic +inline fun P.nextMessageOrNullAsync( + timeoutMillis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline filter: suspend P.(P) -> Boolean = { true } +): Deferred { + require(timeoutMillis > 0) { "timeoutMillis must be > 0" } + return this.bot.async(coroutineContext) { + nextMessageOrNull(timeoutMillis, filter) + } +} + + +@PlannedRemoval("1.2.0") +@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) +@Suppress("DEPRECATION_ERROR") +@JvmSynthetic +fun ContactMessage.isContextIdenticalWith(another: ContactMessage): Boolean { + return this.sender == another.sender && this.subject == another.subject && this.bot == another.bot +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotations.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotations.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index 8c865b445..af2f1fc6a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -10,11 +10,13 @@ package net.mamoe.mirai.utils +import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.coroutineContext +import kotlin.jvm.JvmField import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic @@ -23,30 +25,20 @@ import kotlin.jvm.JvmStatic */ @Suppress("PropertyName") open class BotConfiguration { - /** - * 日志记录器 - */ + /** 日志记录器 */ var botLoggerSupplier: ((Bot) -> MiraiLogger) = { DefaultLogger("Bot(${it.id})") } - /** - * 网络层日志构造器 - */ + /** 网络层日志构造器 */ @OptIn(MiraiInternalAPI::class) var networkLoggerSupplier: ((BotNetworkHandler) -> MiraiLogger) = { DefaultLogger("Network(${it.bot.id})") } - /** - * 设备信息覆盖. 默认使用随机的设备信息. - */ + /** 设备信息覆盖. 默认使用随机的设备信息. */ var deviceInfo: ((Context) -> DeviceInfo)? = null - /** - * 父 [CoroutineContext] - */ + /** 父 [CoroutineContext]. [Bot] 创建后会覆盖其 [Job], 但会将这个 [Job] 作为父 [Job] */ var parentCoroutineContext: CoroutineContext = EmptyCoroutineContext - /** - * 心跳周期. 过长会导致被服务器断开连接. - */ + /** 心跳周期. 过长会导致被服务器断开连接. */ var heartbeatPeriodMillis: Long = 60.secondsToMillis /** @@ -55,30 +47,51 @@ open class BotConfiguration { */ var heartbeatTimeoutMillis: Long = 2.secondsToMillis - /** - * 心跳失败后的第一次重连前的等待时间. - */ + /** 心跳失败后的第一次重连前的等待时间. */ var firstReconnectDelayMillis: Long = 5.secondsToMillis - /** - * 重连失败后, 继续尝试的每次等待时间 - */ + /** 重连失败后, 继续尝试的每次等待时间 */ var reconnectPeriodMillis: Long = 5.secondsToMillis - /** - * 最多尝试多少次重连 - */ + /** 最多尝试多少次重连 */ var reconnectionRetryTimes: Int = Int.MAX_VALUE - /** - * 验证码处理器 - */ + /** 验证码处理器 */ var loginSolver: LoginSolver = LoginSolver.Default - companion object { + /** 使用协议类型 */ + @SinceMirai("1.0.0") + var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PAD + + /** 缓存策略 */ + @SinceMirai("1.0.0") + @MiraiExperimentalAPI + var fileCacheStrategy: FileCacheStrategy = FileCacheStrategy.PlatformDefault + + @SinceMirai("1.0.0") + enum class MiraiProtocol( + /** 协议模块使用的 ID */ + @JvmField internal val id: Long + ) { /** - * 默认的配置实例 + * Android 手机. + * + * - 与手机冲突 + * - 与平板和电脑不冲突 */ + ANDROID_PHONE(537062845), + + /** + * Android 平板. + * + * - 与平板冲突 + * - 与手机和电脑不冲突 + */ + ANDROID_PAD(537062409) + } + + companion object { + /** 默认的配置实例. 可以进行修改 */ @JvmStatic val Default = BotConfiguration() } @@ -115,15 +128,31 @@ open class BotConfiguration { * ``` */ @ConfigurationDsl - @SinceMirai("0.38.0") - suspend fun inheritCoroutineContext() { + suspend inline fun inheritCoroutineContext() { parentCoroutineContext = coroutineContext } - - @SinceMirai("0.38.0") @DslMarker annotation class ConfigurationDsl + + @SinceMirai("1.0.0") + fun copy(): BotConfiguration { + @OptIn(MiraiExperimentalAPI::class) + return BotConfiguration().also { new -> + new.botLoggerSupplier = botLoggerSupplier + new.networkLoggerSupplier = networkLoggerSupplier + new.deviceInfo = deviceInfo + new.parentCoroutineContext = parentCoroutineContext + new.heartbeatPeriodMillis = heartbeatPeriodMillis + new.heartbeatTimeoutMillis = heartbeatTimeoutMillis + new.firstReconnectDelayMillis = firstReconnectDelayMillis + new.reconnectPeriodMillis = reconnectPeriodMillis + new.reconnectionRetryTimes = reconnectionRetryTimes + new.loginSolver = loginSolver + new.protocol = protocol + new.fileCacheStrategy = fileCacheStrategy + } + } } @OptIn(ExperimentalMultiplatform::class) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Channels.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Channels.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index 03f7fad91..93dd93a35 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -11,18 +11,15 @@ package net.mamoe.mirai.utils -import kotlinx.coroutines.io.ByteReadChannel -import kotlinx.io.InputStream -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.Input -import kotlinx.serialization.InternalSerializationApi import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.User import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.sendTo -import net.mamoe.mirai.message.data.toLongUnsigned +import net.mamoe.mirai.utils.internal.DeferredReusableInput +import net.mamoe.mirai.utils.internal.ReusableInput +import kotlin.jvm.JvmField import kotlin.jvm.JvmSynthetic /** @@ -33,40 +30,16 @@ import kotlin.jvm.JvmSynthetic * @see ExternalImage.sendTo 上传图片并以纯图片消息发送给联系人 * @See ExternalImage.upload 上传图片并得到 [Image] 消息 */ -class ExternalImage private constructor( - val md5: ByteArray, - val input: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor - val inputSize: Long // dont be greater than Int.MAX +class ExternalImage internal constructor( + @JvmField + internal val input: ReusableInput ) { - @Deprecated("changing soon in 1.0.0", level = DeprecationLevel.ERROR) - constructor( - md5: ByteArray, - input: ByteReadChannel, - inputSize: Long // dont be greater than Int.MAX - ) : this(md5, input as Any, inputSize) - - @Deprecated("changing soon in 1.0.0", level = DeprecationLevel.ERROR) - constructor( - md5: ByteArray, - input: Input, - inputSize: Long // dont be greater than Int.MAX - ) : this(md5, input as Any, inputSize) - - @Deprecated("changing soon in 1.0.0", level = DeprecationLevel.ERROR) - constructor( - md5: ByteArray, - input: ByteReadPacket - ) : this(md5, input as Any, input.remaining) - - @Deprecated("changing soon in 1.0.0", level = DeprecationLevel.ERROR) - @OptIn(InternalSerializationApi::class) - constructor( - md5: ByteArray, - input: InputStream - ) : this(md5, input as Any, input.available().toLongUnsigned()) + internal val md5: ByteArray get() = this.input.md5 init { - require(inputSize < 30L * 1024 * 1024) { "file is too big. Maximum is about 20MB" } + if (input !is DeferredReusableInput) { + require(input.size < 30L * 1024 * 1024) { "Image file is too big. Maximum is 30 MiB, but recommended to be 20 MiB" } + } } companion object { @@ -93,10 +66,16 @@ class ExternalImage private constructor( * SHARPP: 1004 */ + override fun toString(): String { + if (input is DeferredReusableInput) { + if (!input.initialized) { + return "ExternalImage(uninitialized)" + } + } + return "ExternalImage(${generateUUID(md5)})" + } - override fun toString(): String = "[ExternalImage(${generateUUID(md5)})]" - - fun calculateImageResourceId(): String = generateImageId(md5) + internal fun calculateImageResourceId(): String = generateImageId(md5) } /** diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/FileCacheStrategy.common.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/FileCacheStrategy.common.kt new file mode 100644 index 000000000..f18ab7d6f --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/FileCacheStrategy.common.kt @@ -0,0 +1,60 @@ +package net.mamoe.mirai.utils + +import kotlinx.io.core.Input +import kotlinx.io.errors.IOException +import net.mamoe.mirai.utils.internal.InputStream + +/** + * 缓存策略. + * + * 图片上传时默认使用文件缓存. + */ +@MiraiExperimentalAPI +expect interface FileCacheStrategy { + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此函数应 close 这个 [Input] + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + fun newImageCache(input: Input): ExternalImage + + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此函数应 close 这个 [InputStream] + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + fun newImageCache(input: InputStream): ExternalImage + + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此 [input] 的内容应是不变的. + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + fun newImageCache(input: ByteArray): ExternalImage + + /** + * 默认的缓存方案. 在 JVM 平台使用系统临时文件. + */ + @MiraiExperimentalAPI + object PlatformDefault : FileCacheStrategy + + /** + * 使用内存直接存储所有图片文件. + */ + object MemoryCache : FileCacheStrategy { + @MiraiExperimentalAPI + @Throws(IOException::class) + override fun newImageCache(input: Input): ExternalImage + + @MiraiExperimentalAPI + @Throws(IOException::class) + override fun newImageCache(input: InputStream): ExternalImage + + @MiraiExperimentalAPI + @Throws(IOException::class) + override fun newImageCache(input: ByteArray): ExternalImage + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt index 04db2c23e..81758bb35 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt @@ -19,14 +19,12 @@ import kotlin.jvm.JvmOverloads /** * Collect all the elements into a [MutableList] then cast it as a [List] */ -@MiraiInternalAPI -fun LockFreeLinkedList.toList(): List = toMutableList() +internal fun LockFreeLinkedList.toList(): List = toMutableList() /** * Collect all the elements into a [MutableList]. */ -@MiraiInternalAPI -fun LockFreeLinkedList.toMutableList(): MutableList { +internal fun LockFreeLinkedList.toMutableList(): MutableList { val list = mutableListOf() this.forEach { list.add(it) } return list @@ -35,14 +33,12 @@ fun LockFreeLinkedList.toMutableList(): MutableList { /** * Collect all the elements into a [MutableSet] then cast it as a [Set] */ -@MiraiInternalAPI -fun LockFreeLinkedList.toSet(): Set = toMutableSet() +internal fun LockFreeLinkedList.toSet(): Set = toMutableSet() /** * Collect all the elements into a [MutableSet]. */ -@MiraiInternalAPI -fun LockFreeLinkedList.toMutableSet(): MutableSet { +internal fun LockFreeLinkedList.toMutableSet(): MutableSet { val list = mutableSetOf() this.forEach { list.add(it) } return list @@ -53,15 +49,13 @@ fun LockFreeLinkedList.toMutableSet(): MutableSet { * * Note that the sequence is dynamic */ -@MiraiInternalAPI -fun LockFreeLinkedList.asSequence(): Sequence { +internal fun LockFreeLinkedList.asSequence(): Sequence { return generateSequence(head) { current: LockFreeLinkedListNode -> current.nextValidNode(until = tail).takeIf { it != tail } }.drop(1) // drop head, should be dropped lazily .map { it.nodeValue } } -@OptIn(MiraiInternalAPI::class) internal fun LockFreeLinkedListNode.nextValidNode(until: LockFreeLinkedListNode): LockFreeLinkedListNode { var node: LockFreeLinkedListNode = this.nextNode while (node != until) { @@ -73,24 +67,21 @@ internal fun LockFreeLinkedListNode.nextValidNode(until: LockFreeLinkedLi return node } -@MiraiInternalAPI -operator fun LockFreeLinkedList.iterator(): Iterator { +internal operator fun LockFreeLinkedList.iterator(): Iterator { return asSequence().iterator() } /** * 构建链表结构然后转为 [LockFreeLinkedList] */ -@MiraiInternalAPI -fun Iterable.toLockFreeLinkedList(): LockFreeLinkedList { +internal fun Iterable.toLockFreeLinkedList(): LockFreeLinkedList { return LockFreeLinkedList().apply { addAll(this@toLockFreeLinkedList) } } /** * 构建链表结构然后转为 [LockFreeLinkedList] */ -@MiraiInternalAPI -fun Sequence.toLockFreeLinkedList(): LockFreeLinkedList { +internal fun Sequence.toLockFreeLinkedList(): LockFreeLinkedList { return LockFreeLinkedList().apply { addAll(this@toLockFreeLinkedList) } } @@ -100,9 +91,7 @@ fun Sequence.toLockFreeLinkedList(): LockFreeLinkedList { * Modifying can be performed concurrently. * Iterating concurrency is guaranteed. */ -@PlannedRemoval("1.0.0") // make internal -@MiraiInternalAPI("This is unstable API and is going to be internal in 1.0.0") -open class LockFreeLinkedList { +internal open class LockFreeLinkedList { @PublishedApi internal val tail: Tail = Tail() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SoftRef.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SoftRef.kt index cb45c9ef9..5e155e4cd 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SoftRef.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/SoftRef.kt @@ -43,7 +43,6 @@ inline operator fun UnsafeSoftRef.getValue(thisRef: Any?, property: KProp * @see softRef provides a SoftRef * @see unsafeSoftRef provides a UnsafeSoftRef */ -@SinceMirai("0.32.0") expect class SoftRef(referent: T) { fun get(): T? fun clear() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/utils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TimeUtils.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/utils.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/TimeUtils.kt diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/chunked.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/ChunkedFlowSession.kt similarity index 85% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/chunked.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/ChunkedFlowSession.kt index 27608dad4..5ca9dcb8e 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/io/chunked.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/ChunkedFlowSession.kt @@ -7,20 +7,33 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.qqandroid.utils.io +package net.mamoe.mirai.utils.internal import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.io.ByteReadChannel import kotlinx.io.InputStream import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.Closeable import kotlinx.io.core.Input -import kotlinx.io.pool.useInstance -import net.mamoe.mirai.utils.MiraiInternalAPI import kotlinx.serialization.InternalSerializationApi -import net.mamoe.mirai.qqandroid.utils.ByteArrayPool +import kotlin.jvm.JvmField + + +internal interface ChunkedFlowSession : Closeable { + val flow: Flow + override fun close() +} + +internal inline fun ChunkedFlowSession.map(crossinline mapper: suspend ChunkedFlowSession.(T) -> R): ChunkedFlowSession { + return object : ChunkedFlowSession { + override val flow: Flow = this@map.flow.map { this@map.mapper(it) } + override fun close() = this@map.close() + } +} /** @@ -34,13 +47,13 @@ internal class ChunkedInput( * * **注意**: 不要将他带出 [Flow.collect] 作用域, 否则将造成内存泄露 */ - val buffer: ByteArray, - internal var size: Int + @JvmField val buffer: ByteArray, + @JvmField internal var size: Int ) { /** * [buffer] 的有效大小 */ - val bufferSize: Int get() = size + inline val bufferSize: Int get() = size } /** @@ -51,7 +64,6 @@ internal class ChunkedInput( * * 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence] */ -@OptIn(MiraiInternalAPI::class) internal fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow { ByteArrayPool.checkBufferSize(sizePerPacket) if (this.remaining <= sizePerPacket.toLong()) { @@ -81,7 +93,6 @@ internal fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow * 对于一个 1000 长度的 [ByteReadChannel] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence], * 其长度分别为: 300, 300, 300, 100. */ -@OptIn(MiraiInternalAPI::class) internal fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow { ByteArrayPool.checkBufferSize(sizePerPacket) if (this.isClosedForRead) { @@ -105,7 +116,7 @@ internal fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow * 对于一个 1000 长度的 [Input] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence], * 其长度分别为: 300, 300, 300, 100. */ -@OptIn(MiraiInternalAPI::class, ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) internal fun Input.chunkedFlow(sizePerPacket: Int): Flow { ByteArrayPool.checkBufferSize(sizePerPacket) @@ -132,9 +143,7 @@ internal fun Input.chunkedFlow(sizePerPacket: Int): Flow { * * 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence] */ -@OptIn( - MiraiInternalAPI::class, ExperimentalCoroutinesApi::class, InternalSerializationApi::class -) +@OptIn(ExperimentalCoroutinesApi::class, InternalSerializationApi::class) internal fun InputStream.chunkedFlow(sizePerPacket: Int): Flow { ByteArrayPool.checkBufferSize(sizePerPacket) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/DeferredReusableInput.common.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/DeferredReusableInput.common.kt new file mode 100644 index 000000000..bd0016dad --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/DeferredReusableInput.common.kt @@ -0,0 +1,11 @@ +package net.mamoe.mirai.utils.internal + +import net.mamoe.mirai.utils.FileCacheStrategy +import net.mamoe.mirai.utils.MiraiExperimentalAPI + +internal expect class DeferredReusableInput(input: Any, extraArg: Any?) : ReusableInput { + val initialized: Boolean + + @OptIn(MiraiExperimentalAPI::class) + suspend fun init(strategy: FileCacheStrategy) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/ReusableInput.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/ReusableInput.kt new file mode 100644 index 000000000..9eab67eb4 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/ReusableInput.kt @@ -0,0 +1,13 @@ +package net.mamoe.mirai.utils.internal + +import io.ktor.utils.io.ByteWriteChannel +import net.mamoe.mirai.utils.SinceMirai + +@SinceMirai("1.0.0") +internal interface ReusableInput { + val md5: ByteArray + val size: Long + + fun chunkedFlow(sizePerPacket: Int): ChunkedFlowSession + suspend fun writeTo(out: ByteWriteChannel): Long +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendNameRemark.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/asReusableInput.common.kt similarity index 66% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendNameRemark.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/asReusableInput.common.kt index ff83401af..abcf9c24a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/FriendNameRemark.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/asReusableInput.common.kt @@ -7,12 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.data +package net.mamoe.mirai.utils.internal -import net.mamoe.mirai.utils.MiraiExperimentalAPI +internal expect fun ByteArray.asReusableInput(): ReusableInput -/** - * 给好友设置的备注 - */ -@MiraiExperimentalAPI -inline class FriendNameRemark(val value: String) +internal fun asReusableInput0(input: ByteArray): ReusableInput = input.asReusableInput() \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/md5.common.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/md5.common.kt index 9c9864351..6a30b73ac 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/md5.common.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/md5.common.kt @@ -38,11 +38,11 @@ internal inline fun InputStream.readInSequence(block: (ByteArray, len: Int) -> U /** * 缓存 [ByteArray] 实例的 [ObjectPool] */ -internal object ByteArrayPool : DefaultPool(32) { +internal object ByteArrayPool : DefaultPool(256) { /** * 每一个 [ByteArray] 的大小 */ - const val BUFFER_SIZE: Int = 8192 + const val BUFFER_SIZE: Int = 8192 * 8 override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/tryNTimes.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/retryCatching.common.kt similarity index 98% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/tryNTimes.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/retryCatching.common.kt index 285f0d360..e1f294a3d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/tryNTimes.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/internal/retryCatching.common.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.internal import kotlin.reflect.KClass -@PublishedApi internal expect fun Throwable.addSuppressedMirai(e: Throwable) diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ContentEqualsTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ContentEqualsTest.kt index 0c6cba4b0..27987ef22 100644 --- a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ContentEqualsTest.kt +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ContentEqualsTest.kt @@ -19,7 +19,7 @@ internal class ContentEqualsTest { @Test fun testContentEquals() { val mySource = TestConstrainSingleMessage() - val image = Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png") + val image = Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai") assertTrue { buildMessageChain { diff --git a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt index 48f3d94f5..250abcb80 100644 --- a/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt +++ b/mirai-core/src/commonTest/kotlin/net/mamoe/mirai/message.data/ImageTest.kt @@ -39,11 +39,6 @@ internal class ImageTest { calculateImageMd5ByImageId("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai").contentToString() ) - assertEquals( - "01E9451B-70ED-EAE3-B37C-101F1EEBF5B5".filterNot { it == '-' }.autoHexToBytes().contentToString(), - calculateImageMd5ByImageId("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.gif").contentToString() - ) - assertEquals( "f8f1ab55-bf8e-4236-b55e-955848d7069f".filterNot { it == '-' }.autoHexToBytes().contentToString(), calculateImageMd5ByImageId("/f8f1ab55-bf8e-4236-b55e-955848d7069f").contentToString() diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactory.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactory.kt new file mode 100644 index 000000000..0bb569206 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactory.kt @@ -0,0 +1,144 @@ +@file:Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR", "DeprecatedCallableAddReplaceWith") +@file:JvmName("BotFactoryJvm") + +package net.mamoe.mirai + +import net.mamoe.mirai.utils.BotConfiguration +import net.mamoe.mirai.utils.Context +import net.mamoe.mirai.utils.ContextImpl + +/** + * 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式. + * + * 请添加模块依赖 `mirai-core-qqandroid` 以获取协议支持. + * + * ### 自动选择协议模块并构造 [Bot] + * 在 Kotlin 使用包级函数 [Bot], 在 Java 使用 `BotFactoryJvm.newBot` + * + * mirai 通过 [Class.forName] 查找可用的协议实现, 如 `net.mamoe.mirai.qqandroid.QQAndroid` + * + * ### 手动选择协议模块并构造 [Bot] + * 引用 `net.mamoe.mirai.qqandroid.QQAndroid` 并使用其成员函数 [Bot] + */ +actual interface BotFactory { + /** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ + @JvmName("newBot") + actual fun Bot( + context: Context, + qq: Long, + password: String, + configuration: BotConfiguration + ): Bot + + /** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ + @JvmName("newBot") + actual fun Bot( + context: Context, + qq: Long, + passwordMd5: ByteArray, + configuration: BotConfiguration + ): Bot + +} + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmName("newBot") +@JvmOverloads +fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = + factory.Bot(context, qq, password, configuration) + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmSynthetic +inline fun Bot(context: Context, qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot = + factory.Bot(context, qq, password, configuration) + + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmName("newBot") +@JvmOverloads +fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = + factory.Bot(ContextImpl(), qq, password, configuration) + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmSynthetic +inline fun Bot(qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot = + factory.Bot(ContextImpl(), qq, password, configuration) + + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmName("newBot") +@JvmOverloads +fun Bot( + context: Context, + qq: Long, + passwordMd5: ByteArray, + configuration: BotConfiguration = BotConfiguration.Default +): Bot = + factory.Bot(context, qq, passwordMd5, configuration) + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmSynthetic +inline fun Bot(context: Context, qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot = + factory.Bot(context, qq, passwordMd5, BotConfiguration().apply(configuration)) + + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmName("newBot") +@JvmOverloads +fun Bot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration = BotConfiguration.Default): Bot = + factory.Bot(ContextImpl(), qq, passwordMd5, configuration) + +/** + * 自动加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmSynthetic +inline fun Bot(qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot = + factory.Bot(ContextImpl(), qq, passwordMd5, BotConfiguration().apply(configuration)) + + + + + +// Do not use ServiceLoader. Probably not working on MPP +@PublishedApi +internal val factory: BotFactory = run { + runCatching { + Class.forName("net.mamoe.mirai.timpc.TIMPC").kotlin.objectInstance as BotFactory + }.getOrElse { + runCatching { + Class.forName("net.mamoe.mirai.qqandroid.QQAndroid").kotlin.objectInstance as BotFactory + }.getOrNull() + } +} ?: error( + """ + No BotFactory found. Please ensure that you've added dependency of protocol modules. + Available modules: + - net.mamoe:mirai-core-timpc (stays at 0.12.0) + - net.mamoe:mirai-core-qqandroid (recommended) + You should have at lease one protocol module installed. + ------------------------------------------------------- + 找不到 BotFactory. 请确保你依赖了至少一个协议模块. + 可用的协议模块: + - net.mamoe:mirai-core-timpc (0.12.0 后停止更新) + - net.mamoe:mirai-core-qqandroid (推荐) + 请添加上述任一模块的依赖(与 mirai-core 版本相同) + """.trimIndent() +) \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt index d6759268a..726ca00f2 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt @@ -15,97 +15,3 @@ package net.mamoe.mirai import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.ContextImpl - -// Do not use ServiceLoader. Probably not working on MPP -@PublishedApi -internal val factory: BotFactory = run { - runCatching { - Class.forName("net.mamoe.mirai.timpc.TIMPC").kotlin.objectInstance as BotFactory - }.getOrElse { - runCatching { - Class.forName("net.mamoe.mirai.qqandroid.QQAndroid").kotlin.objectInstance as BotFactory - }.getOrNull() - } -} ?: error( - """ - No BotFactory found. Please ensure that you've added dependency of protocol modules. - Available modules: - - net.mamoe:mirai-core-timpc (stays at 0.12.0) - - net.mamoe:mirai-core-qqandroid (recommended) - You should have at lease one protocol module installed. - ------------------------------------------------------- - 找不到 BotFactory. 请确保你依赖了至少一个协议模块. - 可用的协议模块: - - net.mamoe:mirai-core-timpc (0.12.0 后停止更新) - - net.mamoe:mirai-core-qqandroid (推荐) - 请添加上述任一模块的依赖(与 mirai-core 版本相同) - """.trimIndent() -) - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmName("newBot") -@JvmOverloads -fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = - factory.Bot(context, qq, password, configuration) - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmSynthetic -inline fun Bot(context: Context, qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot = - factory.Bot(context, qq, password, configuration) - - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmName("newBot") -@JvmOverloads -fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = - factory.Bot(ContextImpl(), qq, password, configuration) - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmSynthetic -inline fun Bot(qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot = - factory.Bot(ContextImpl(), qq, password, configuration) - - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmName("newBot") -@JvmOverloads -fun Bot( - context: Context, - qq: Long, - passwordMd5: ByteArray, - configuration: BotConfiguration = BotConfiguration.Default -): Bot = - factory.Bot(context, qq, passwordMd5, configuration) - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmSynthetic -inline fun Bot(context: Context, qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot = - factory.Bot(context, qq, passwordMd5, BotConfiguration().apply(configuration)) - - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmName("newBot") -@JvmOverloads -fun Bot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration = BotConfiguration.Default): Bot = - factory.Bot(ContextImpl(), qq, passwordMd5, configuration) - -/** - * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 - */ -@JvmSynthetic -inline fun Bot(qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot = - factory.Bot(ContextImpl(), qq, passwordMd5, BotConfiguration().apply(configuration)) \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotJavaFriendlyAPI.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotJavaFriendlyAPI.kt index ef3295b71..487c3292e 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotJavaFriendlyAPI.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotJavaFriendlyAPI.kt @@ -2,7 +2,6 @@ package net.mamoe.mirai import kotlinx.coroutines.* import net.mamoe.mirai.contact.recall -import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain @@ -119,23 +118,6 @@ actual abstract class BotJavaFriendlyAPI actual constructor() { runBlocking { join() } } - /** - * 添加一个好友 - * - * @param message 若需要验证请求时的验证消息. - * @param remark 好友备注 - */ - @OptIn(MiraiExperimentalAPI::class) - @JvmName("addFriend") - fun __addFriendBlockingForJava__( - id: Long, - message: String? = null, - remark: String? = null - ): AddFriendResult { - @OptIn(MiraiExperimentalAPI::class) - return runBlocking { addFriend(id, message, remark) } - } - /** * 异步调用 [__loginBlockingForJava__] */ diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt index 6e0b96bcf..faaa2c04d 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaFriendlyAPI.kt @@ -208,7 +208,7 @@ actual abstract class ContactJavaFriendlyAPI { @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused", "unused", "DEPRECATION_ERROR") @MiraiInternalAPI @JavaFriendlyAPI -actual abstract class MemberJavaFriendlyAPI : QQ() { +actual abstract class MemberJavaFriendlyAPI : User() { private inline fun runBlocking(crossinline block: suspend Member.() -> R): R { @Suppress("CAST_NEVER_SUCCEEDS") return kotlinx.coroutines.runBlocking { block(this@MemberJavaFriendlyAPI as Member) } diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageEventPlatform.kt similarity index 55% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageEventPlatform.kt index ff0df3075..c7a1656c5 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageEventPlatform.kt @@ -7,50 +7,45 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("unused") +@file:Suppress("unused", "DECLARATION_CANT_BE_INLINED") package net.mamoe.mirai.message -import kotlinx.coroutines.io.ByteWriteChannel import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlinx.io.core.use +import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.User import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.copyAndClose -import net.mamoe.mirai.utils.copyTo +import net.mamoe.mirai.message.data.MessageChain import java.awt.image.BufferedImage import java.io.File import java.io.InputStream -import java.io.OutputStream import java.net.URL /** - * 一条从服务器接收到的消息事件. - * JVM 平台相关扩展 + * 消息事件在 JVM 平台的扩展 + * @see MessageEventExtensions */ -@Suppress("DEPRECATION") -@Deprecated( - message = "use ContactMessage", - replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage") -) -@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) -actual abstract class MessagePacket actual constructor() : - MessagePacketBase() { +internal actual interface MessageEventPlatformExtensions { + actual val subject: TSubject + actual val sender: TSender + actual val message: MessageChain + actual val bot: Bot + // region 上传图片 + @JvmSynthetic suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) @JvmSynthetic suspend inline fun uploadImage(image: URL): Image = subject.uploadImage(image) + @JvmSynthetic suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image) + @JvmSynthetic suspend inline fun uploadImage(image: InputStream): Image = subject.uploadImage(image) + @JvmSynthetic suspend inline fun uploadImage(image: File): Image = subject.uploadImage(image) // endregion @@ -58,12 +53,16 @@ actual abstract class MessagePacket actual c // region 发送图片 @JvmSynthetic suspend inline fun sendImage(image: BufferedImage): MessageReceipt = subject.sendImage(image) + @JvmSynthetic suspend inline fun sendImage(image: URL): MessageReceipt = subject.sendImage(image) + @JvmSynthetic suspend inline fun sendImage(image: Input): MessageReceipt = subject.sendImage(image) + @JvmSynthetic suspend inline fun sendImage(image: InputStream): MessageReceipt = subject.sendImage(image) + @JvmSynthetic suspend inline fun sendImage(image: File): MessageReceipt = subject.sendImage(image) // endregion @@ -71,12 +70,16 @@ actual abstract class MessagePacket actual c // region 上传图片 (扩展) @JvmSynthetic suspend inline fun BufferedImage.upload(): Image = upload(subject) + @JvmSynthetic suspend inline fun URL.uploadAsImage(): Image = uploadAsImage(subject) + @JvmSynthetic suspend inline fun Input.uploadAsImage(): Image = uploadAsImage(subject) + @JvmSynthetic suspend inline fun InputStream.uploadAsImage(): Image = uploadAsImage(subject) + @JvmSynthetic suspend inline fun File.uploadAsImage(): Image = uploadAsImage(subject) // endregion 上传图片 (扩展) @@ -84,60 +87,17 @@ actual abstract class MessagePacket actual c // region 发送图片 (扩展) @JvmSynthetic suspend inline fun BufferedImage.send(): MessageReceipt = sendTo(subject) + @JvmSynthetic suspend inline fun URL.sendAsImage(): MessageReceipt = sendAsImageTo(subject) + @JvmSynthetic suspend inline fun Input.sendAsImage(): MessageReceipt = sendAsImageTo(subject) + @JvmSynthetic suspend inline fun InputStream.sendAsImage(): MessageReceipt = sendAsImageTo(subject) + @JvmSynthetic suspend inline fun File.sendAsImage(): MessageReceipt = sendAsImageTo(subject) // endregion 发送图片 (扩展) - - // region 下载图片 (扩展) - @JvmSynthetic - suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) } - - /** - * 下载图片到 [output] 但不关闭这个 [output] - */ - @JvmSynthetic - suspend inline fun Image.downloadTo(output: OutputStream) = channel().copyTo(output) - - /** - * 下载图片到 [output] 并关闭这个 [output] - */ - @JvmSynthetic - suspend inline fun Image.downloadAndClose(output: OutputStream) = channel().copyAndClose(output) - - /** - * 下载图片到 [output] 但不关闭这个 [output] - */ - @JvmSynthetic - suspend inline fun Image.downloadTo(output: Output) = channel().copyTo(output) - - /** - * 下载图片到 [output] 并关闭这个 [output] - */ - @JvmSynthetic - suspend inline fun Image.downloadAndClose(output: Output) = channel().copyAndClose(output) - - /** - * 下载图片到 [output] 但不关闭这个 [output] - */ - @JvmSynthetic - suspend inline fun Image.downloadTo(output: ByteWriteChannel) = channel().copyTo(output) - - /** - * 下载图片到 [output] 并关闭这个 [output] - */ - @JvmSynthetic - suspend inline fun Image.downloadAndClose(output: ByteWriteChannel) = channel().copyAndClose(output) - - /* - suspend inline fun Image.downloadAsStream(): InputStream = channel().asInputStream() - suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() } - suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(Dispatchers.IO) { ImageIO.read(downloadAsStream()) } - */ - // endregion } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt index c01a42aa9..d540d2859 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt @@ -37,7 +37,7 @@ import java.net.URL */ @Throws(OverFileSizeMaxException::class) suspend fun BufferedImage.sendTo(contact: C): MessageReceipt = - withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + toExternalImage().sendTo(contact) /** * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 @@ -45,7 +45,7 @@ suspend fun BufferedImage.sendTo(contact: C): MessageReceipt = */ @Throws(OverFileSizeMaxException::class) suspend fun URL.sendAsImageTo(contact: C): MessageReceipt = - withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + toExternalImage().sendTo(contact) /** * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 @@ -53,7 +53,7 @@ suspend fun URL.sendAsImageTo(contact: C): MessageReceipt = */ @Throws(OverFileSizeMaxException::class) suspend fun Input.sendAsImageTo(contact: C): MessageReceipt = - withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + toExternalImage().sendTo(contact) /** * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 @@ -61,7 +61,7 @@ suspend fun Input.sendAsImageTo(contact: C): MessageReceipt = */ @Throws(OverFileSizeMaxException::class) suspend fun InputStream.sendAsImageTo(contact: C): MessageReceipt = - withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + toExternalImage().sendTo(contact) /** * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人 @@ -70,7 +70,7 @@ suspend fun InputStream.sendAsImageTo(contact: C): MessageReceipt< @Throws(OverFileSizeMaxException::class) suspend fun File.sendAsImageTo(contact: C): MessageReceipt { require(this.exists() && this.canRead()) - return withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + return toExternalImage().sendTo(contact) } // endregion @@ -84,7 +84,7 @@ suspend fun File.sendAsImageTo(contact: C): MessageReceipt { @JvmSynthetic @Throws(OverFileSizeMaxException::class) suspend fun BufferedImage.upload(contact: Contact): Image = - withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + toExternalImage().upload(contact) /** * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传后构造 [Image] @@ -92,7 +92,7 @@ suspend fun BufferedImage.upload(contact: Contact): Image = */ @Throws(OverFileSizeMaxException::class) suspend fun URL.uploadAsImage(contact: Contact): Image = - withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + toExternalImage().upload(contact) /** * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传后构造 [Image] @@ -100,7 +100,7 @@ suspend fun URL.uploadAsImage(contact: Contact): Image = */ @Throws(OverFileSizeMaxException::class) suspend fun Input.uploadAsImage(contact: Contact): Image = - withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + toExternalImage().upload(contact) /** * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image] @@ -108,7 +108,7 @@ suspend fun Input.uploadAsImage(contact: Contact): Image = */ @Throws(OverFileSizeMaxException::class) suspend fun InputStream.uploadAsImage(contact: Contact): Image = - withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + toExternalImage().upload(contact) /** * 在 [Dispatchers.IO] 中将文件作为图片上传后构造 [Image] @@ -117,7 +117,7 @@ suspend fun InputStream.uploadAsImage(contact: Contact): Image = @Throws(OverFileSizeMaxException::class) suspend fun File.uploadAsImage(contact: Contact): Image { require(this.isFile && this.exists() && this.canRead()) { "file ${this.path} is not readable" } - return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + return toExternalImage().upload(contact) } // endregion diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt index def08db8c..8dafa52ce 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/data/Image.kt @@ -44,6 +44,9 @@ import java.net.URL * @see URL.sendAsImageTo * * + * ### 下载图片 + * @see Image.queryUrl 查询图片下载链接 + * * * @see FlashImage 闪照 * @see Image.flash 转换普通图片为闪照 diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt index fc13c18d5..85bc40311 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt @@ -11,21 +11,14 @@ package net.mamoe.mirai.utils -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.io.ByteReadChannel -import kotlinx.coroutines.withContext import kotlinx.io.core.Input -import kotlinx.io.core.copyTo -import kotlinx.io.errors.IOException -import kotlinx.io.streams.asOutput -import net.mamoe.mirai.utils.internal.md5 +import net.mamoe.mirai.Bot +import net.mamoe.mirai.utils.internal.DeferredReusableInput +import net.mamoe.mirai.utils.internal.asReusableInput import java.awt.image.BufferedImage import java.io.File import java.io.InputStream -import java.io.OutputStream import java.net.URL -import java.security.MessageDigest -import javax.imageio.ImageIO /* * 将各类型图片容器转为 [ExternalImage] @@ -33,127 +26,55 @@ import javax.imageio.ImageIO /** - * 将 [BufferedImage] 保存稳临时文件, 然后构造 [ExternalImage] + * 将 [BufferedImage] 保存为临时文件, 然后构造 [ExternalImage] */ @JvmOverloads -@Throws(IOException::class) -fun BufferedImage.toExternalImage(formatName: String = "png"): ExternalImage { - val file = createTempFile().apply { deleteOnExit() } - - val digest = MessageDigest.getInstance("md5") - digest.reset() - - file.outputStream().use { out -> - ImageIO.write(this@toExternalImage, formatName, object : OutputStream() { - override fun write(b: Int) { - out.write(b) - digest.update(b.toByte()) - } - - override fun write(b: ByteArray) { - out.write(b) - digest.update(b) - } - - override fun write(b: ByteArray, off: Int, len: Int) { - out.write(b, off, len) - digest.update(b, off, len) - } - }) - } - - @Suppress("DEPRECATION_ERROR") - return ExternalImage(digest.digest(), file.inputStream()) -} - -suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +fun BufferedImage.toExternalImage(formatName: String = "png"): ExternalImage = + ExternalImage(DeferredReusableInput(this, formatName)) /** - * 直接使用文件 [inputStream] 构造 [ExternalImage] + * 将文件作为 [ExternalImage] 使用. 只会在需要的时候打开文件并读取数据. + * @param deleteOnClose 若为 `true`, 图片发送后将会删除这个文件 */ -@OptIn(MiraiInternalAPI::class) -@Throws(IOException::class) -fun File.toExternalImage(): ExternalImage { - @Suppress("DEPRECATION_ERROR") - return ExternalImage( - md5 = this.inputStream().md5(), // dont change - input = this.inputStream() - ) -} +@JvmOverloads +fun File.toExternalImage(deleteOnClose: Boolean = false): ExternalImage = ExternalImage(asReusableInput(deleteOnClose)) /** - * 在 [IO] 中进行 [File.toExternalImage] + * 将 [URL] 委托为 [ExternalImage]. + * 只会在上传图片时才读取 [URL] 的内容. 具体行为取决于相关 [Bot] 的 [FileCacheStrategy] */ -suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +fun URL.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) /** - * 下载文件到临时目录然后调用 [File.toExternalImage] + * 将 [InputStream] 委托为 [ExternalImage]. + * 只会在上传图片时才读取 [InputStream] 的内容. 具体行为取决于相关 [Bot] 的 [FileCacheStrategy] */ -@Throws(IOException::class) -fun URL.toExternalImage(): ExternalImage { - val file = createTempFile().apply { deleteOnExit() } - file.outputStream().use { output -> - openStream().use { input -> - input.copyTo(output) - } - output.flush() - } - return file.toExternalImage() -} +@JvmName("toExternalImage") +fun InputStream.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) /** - * 在 [IO] 中进行 [URL.toExternalImage] + * 将 [Input] 委托为 [ExternalImage]. + * 只会在上传图片时才读取 [Input] 的内容. 具体行为取决于相关 [Bot] 的 [FileCacheStrategy] */ -suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +fun Input.toExternalImage(): ExternalImage = ExternalImage(DeferredReusableInput(this, null)) -/** - * 保存为临时文件然后调用 [File.toExternalImage] - */ -@Throws(IOException::class) -fun InputStream.toExternalImage(): ExternalImage { - val file = createTempFile().apply { deleteOnExit() } - file.outputStream().use { - this.copyTo(it) - it.flush() - } - this.close() - return file.toExternalImage() -} -/** - * 在 [IO] 中进行 [InputStream.toExternalImage] - */ -suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +@PlannedRemoval("1.2.0") +@Deprecated("no need", ReplaceWith("toExternalImage()")) +fun Input.suspendToExternalImage(): ExternalImage = toExternalImage() -/** - * 保存为临时文件然后调用 [File.toExternalImage]. - * - * 需要函数调用者 close [this] - */ -@Throws(IOException::class) -fun Input.toExternalImage(): ExternalImage { - val file = createTempFile().apply { deleteOnExit() } - file.outputStream().asOutput().use { - this.copyTo(it) - it.flush() - } - return file.toExternalImage() -} +@PlannedRemoval("1.2.0") +@Deprecated("no need", ReplaceWith("toExternalImage()")) +fun InputStream.suspendToExternalImage(): ExternalImage = toExternalImage() -/** - * 在 [IO] 中进行 [Input.toExternalImage] - */ -suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +@PlannedRemoval("1.2.0") +@Deprecated("no need", ReplaceWith("toExternalImage()")) +fun URL.suspendToExternalImage(): ExternalImage = toExternalImage() -/** - * 保存为临时文件然后调用 [File.toExternalImage]. - */ -suspend fun ByteReadChannel.toExternalImage(): ExternalImage { - val file = createTempFile().apply { deleteOnExit() } - file.outputStream().use { - withContext(IO) { copyTo(it) } - it.flush() - } +@PlannedRemoval("1.2.0") +@Deprecated("no need", ReplaceWith("toExternalImage()")) +fun File.suspendToExternalImage(): ExternalImage = toExternalImage() - return file.suspendToExternalImage() -} \ No newline at end of file +@PlannedRemoval("1.2.0") +@Deprecated("no need", ReplaceWith("toExternalImage()")) +fun BufferedImage.suspendToExternalImage(): ExternalImage = toExternalImage() diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/FileCacheStrategy.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/FileCacheStrategy.jvm.kt new file mode 100644 index 000000000..2385ae4af --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/FileCacheStrategy.jvm.kt @@ -0,0 +1,213 @@ +@file:Suppress("MemberVisibilityCanBePrivate") + +package net.mamoe.mirai.utils + +import kotlinx.io.core.Closeable +import kotlinx.io.core.Input +import kotlinx.io.core.readAvailable +import kotlinx.io.core.readBytes +import net.mamoe.mirai.Bot +import net.mamoe.mirai.utils.internal.InputStream +import net.mamoe.mirai.utils.internal.asReusableInput +import java.awt.image.BufferedImage +import java.io.ByteArrayOutputStream +import java.io.File +import java.io.IOException +import java.io.OutputStream +import java.net.URL +import java.security.MessageDigest +import javax.imageio.ImageIO +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +/** + * 缓存策略. + * + * 图片上传时默认使用文件缓存. + * + * @see BotConfiguration.fileCacheStrategy 为 [Bot] 指定缓存策略 + */ +@MiraiExperimentalAPI +actual interface FileCacheStrategy { + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此函数应 close 这个 [Input] + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + actual fun newImageCache(input: Input): ExternalImage + + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此函数应 close 这个 [InputStream] + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + actual fun newImageCache(input: InputStream): ExternalImage + + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此 [input] 的内容应是不变的. + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + actual fun newImageCache(input: ByteArray): ExternalImage + + /** + * 将 [input] 缓存为 [ExternalImage]. + * 此 [input] 的内容应是不变的. + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + fun newImageCache(input: BufferedImage, format: String = "png"): ExternalImage + + /** + * 将 [input] 缓存为 [ExternalImage]. + */ + @MiraiExperimentalAPI + @Throws(IOException::class) + fun newImageCache(input: URL, format: String = "png"): ExternalImage + + /** + * 默认的缓存方案, 使用系统临时文件夹存储. + */ + @MiraiExperimentalAPI + actual object PlatformDefault : FileCacheStrategy by TempCache(null) + + /** + * 使用内存直接存储所有图片文件. + */ + actual object MemoryCache : FileCacheStrategy { + @MiraiExperimentalAPI + @Throws(IOException::class) + actual override fun newImageCache(input: Input): ExternalImage { + return newImageCache(input.readBytes()) + } + + @MiraiExperimentalAPI + @Throws(IOException::class) + actual override fun newImageCache(input: InputStream): ExternalImage { + return newImageCache(input.readBytes()) + } + + @MiraiExperimentalAPI + @Throws(IOException::class) + actual override fun newImageCache(input: ByteArray): ExternalImage { + return ExternalImage(input.asReusableInput()) + } + + @MiraiExperimentalAPI + override fun newImageCache(input: BufferedImage, format: String): ExternalImage { + val out = ByteArrayOutputStream() + ImageIO.write(input, format, out) + return newImageCache(out.toByteArray()) + } + + @MiraiExperimentalAPI + override fun newImageCache(input: URL, format: String): ExternalImage { + val out = ByteArrayOutputStream() + input.openConnection().getInputStream().use { it.copyTo(out) } + return newImageCache(out.toByteArray()) + } + } + + /** + * 使用系统临时文件夹缓存图片文件. 在图片使用完毕后删除临时文件. + */ + @MiraiExperimentalAPI + class TempCache @JvmOverloads constructor( + /** + * 缓存图片存放位置 + */ + val directory: File? = null + ) : FileCacheStrategy { + @MiraiExperimentalAPI + @Throws(IOException::class) + override fun newImageCache(input: Input): ExternalImage { + return ExternalImage(createTempFile(directory = directory).apply { + deleteOnExit() + input.withOut(this.outputStream()) { copyTo(it) } + }.asReusableInput(true)) + } + + @MiraiExperimentalAPI + @Throws(IOException::class) + override fun newImageCache(input: InputStream): ExternalImage { + return ExternalImage(createTempFile(directory = directory).apply { + deleteOnExit() + input.withOut(this.outputStream()) { copyTo(it) } + }.asReusableInput(true)) + } + + @MiraiExperimentalAPI + @Throws(IOException::class) + override fun newImageCache(input: ByteArray): ExternalImage { + return ExternalImage(input.asReusableInput()) + } + + @MiraiExperimentalAPI + override fun newImageCache(input: BufferedImage, format: String): ExternalImage { + val file = createTempFile(directory = directory).apply { deleteOnExit() } + + val digest = MessageDigest.getInstance("md5") + digest.reset() + + file.outputStream().use { out -> + ImageIO.write(input, format, object : OutputStream() { + override fun write(b: Int) { + out.write(b) + digest.update(b.toByte()) + } + + override fun write(b: ByteArray) { + out.write(b) + digest.update(b) + } + + override fun write(b: ByteArray, off: Int, len: Int) { + out.write(b, off, len) + digest.update(b, off, len) + } + }) + } + + @Suppress("DEPRECATION_ERROR") + return ExternalImage(file.asReusableInput(true, digest.digest())) + } + + @MiraiExperimentalAPI + override fun newImageCache(input: URL, format: String): ExternalImage { + return ExternalImage(createTempFile(directory = directory).apply { + deleteOnExit() + input.openConnection().getInputStream().withOut(this.outputStream()) { copyTo(it) } + }.asReusableInput(true)) + } + } +} + +@OptIn(ExperimentalContracts::class) +internal inline fun I.withOut(output: O, block: I.(output: O) -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return use { output.use { block(this, output) } } +} + +/** + * Copies this stream to the given output stream, returning the number of bytes copied + * + * **Note** It is the caller's responsibility to close both of these resources. + */ +@Throws(IOException::class) +internal fun Input.copyTo(out: OutputStream, bufferSize: Int = DEFAULT_BUFFER_SIZE): Long { + var bytesCopied: Long = 0 + val buffer = ByteArray(bufferSize) + var bytes = readAvailable(buffer) + while (bytes >= 0) { + out.write(buffer, 0, bytes) + bytesCopied += bytes + bytes = readAvailable(buffer) + } + return bytesCopied +} diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt index 3857b61c2..0855c4cc6 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/LoginSolver.swing.jvm.kt @@ -20,7 +20,6 @@ import javax.swing.JTextField /** * @author Karlatemp */ -@SinceMirai("0.39.2") @MiraiExperimentalAPI object SwingSolver : LoginSolver() { override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? { diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt index c9487dc6f..1de87c9f7 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/WindowHelperJvm.kt @@ -22,6 +22,7 @@ import java.awt.event.WindowAdapter import java.awt.event.WindowEvent import javax.swing.JFrame import javax.swing.JTextField +import javax.swing.SwingUtilities // 隔离类代码 internal object WindowHelperJvm { @@ -88,7 +89,9 @@ internal suspend fun openWindow(title: String = "", initializer: WindowInitialzi frame.title = title frame.isVisible = true - val result = def.await() - frame.dispose() + val result = def.await().trim() + SwingUtilities.invokeLater { + frame.dispose() + } return result } 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 new file mode 100644 index 000000000..974acb445 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/DeferredReusableInput.jvm.kt @@ -0,0 +1,49 @@ +package net.mamoe.mirai.utils.internal + +import io.ktor.utils.io.ByteWriteChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.io.core.Input +import net.mamoe.mirai.utils.FileCacheStrategy +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import java.awt.image.BufferedImage +import java.net.URL + +internal actual class DeferredReusableInput actual constructor( + val input: Any, + val extraArg: Any? +) : ReusableInput { + + + @OptIn(MiraiExperimentalAPI::class) + actual suspend fun init(strategy: FileCacheStrategy) = withContext(Dispatchers.IO) { + if (delegate != null) { + return@withContext + } + delegate = when (input) { + 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) + else -> error("Internal error: unsupported DeferredReusableInput.input: ${input::class.qualifiedName}") + }.input + } + + private var delegate: ReusableInput? = null + + override val md5: ByteArray + get() = delegate?.md5 ?: error("DeferredReusableInput not yet initialized") + override val size: Long + get() = delegate?.size ?: error("DeferredReusableInput not yet initialized") + + override fun chunkedFlow(sizePerPacket: Int): ChunkedFlowSession { + return delegate?.chunkedFlow(sizePerPacket) ?: error("DeferredReusableInput not yet initialized") + } + + override suspend fun writeTo(out: ByteWriteChannel): Long { + return delegate?.writeTo(out) ?: error("DeferredReusableInput not yet initialized") + } + + actual val initialized: Boolean get() = delegate != null +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/asReusableInput.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/asReusableInput.jvm.kt new file mode 100644 index 000000000..168b043f8 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/asReusableInput.jvm.kt @@ -0,0 +1,93 @@ +package net.mamoe.mirai.utils.internal + +import io.ktor.utils.io.ByteWriteChannel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext +import net.mamoe.mirai.message.data.toLongUnsigned +import java.io.File +import java.io.InputStream + +internal actual fun ByteArray.asReusableInput(): ReusableInput { + return object : ReusableInput { + override val md5: ByteArray = md5() + override val size: Long get() = this@asReusableInput.size.toLongUnsigned() + + override fun chunkedFlow(sizePerPacket: Int): ChunkedFlowSession { + return object : ChunkedFlowSession { + override val flow: Flow = inputStream().chunkedFlow(sizePerPacket) + + override fun close() { + // nothing to do + } + } + } + + override suspend fun writeTo(out: ByteWriteChannel): Long { + out.writeFully(this@asReusableInput, 0, this@asReusableInput.size) + out.flush() + return this@asReusableInput.size.toLongUnsigned() + } + } +} + +internal fun File.asReusableInput(deleteOnClose: Boolean): ReusableInput { + return object : ReusableInput { + override val md5: ByteArray = inputStream().use { it.md5() } + override val size: Long get() = length() + + override fun chunkedFlow(sizePerPacket: Int): ChunkedFlowSession { + val stream = inputStream() + return object : ChunkedFlowSession { + override val flow: Flow = stream.chunkedFlow(sizePerPacket) + override fun close() { + stream.close() + if (deleteOnClose) this@asReusableInput.delete() + } + } + } + + override suspend fun writeTo(out: ByteWriteChannel): Long { + return inputStream().use { it.copyTo(out) } + } + } +} + +internal fun File.asReusableInput(deleteOnClose: Boolean, md5: ByteArray): ReusableInput { + return object : ReusableInput { + override val md5: ByteArray get() = md5 + override val size: Long get() = length() + + override fun chunkedFlow(sizePerPacket: Int): ChunkedFlowSession { + val stream = inputStream() + return object : ChunkedFlowSession { + override val flow: Flow = stream.chunkedFlow(sizePerPacket) + override fun close() { + stream.close() + if (deleteOnClose) this@asReusableInput.delete() + } + } + } + + override suspend fun writeTo(out: ByteWriteChannel): Long { + return inputStream().use { it.copyTo(out) } + } + } +} + +private suspend fun InputStream.copyTo(out: ByteWriteChannel): Long = withContext(Dispatchers.IO) { + var bytesCopied: Long = 0 + + ByteArrayPool.useInstance { buffer -> + var bytes = read(buffer) + while (bytes >= 0) { + out.writeFully(buffer, 0, bytes) + bytesCopied += bytes + bytes = read(buffer) + } + } + + out.flush() + + return@withContext bytesCopied +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.jvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/retryCatching.jvm.kt similarity index 96% rename from mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.jvm.kt rename to mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/retryCatching.jvm.kt index 1fd698979..94664c453 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/addSuppressed.jvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/internal/retryCatching.jvm.kt @@ -2,7 +2,6 @@ package net.mamoe.mirai.utils.internal private var isAddSuppressedSupported: Boolean = true -@PublishedApi internal actual fun Throwable.addSuppressedMirai(e: Throwable) { if (this == e) { return