Merge pull request #304 from mamoe/1.0.0

1.0.0
This commit is contained in:
Him188 2020-05-05 16:10:32 +08:00 committed by GitHub
commit 8ca4357eb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 1666 additions and 4593 deletions

View File

@ -101,7 +101,7 @@
#### `OfflineMessageSource` 构造 #### `OfflineMessageSource` 构造
可使用 DSL 构造离线消息, 修改其发送人, 发送时间, 发送内容等. 这对于跨群转发等情况十分有用. 可使用 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 总览: DSL 总览:
``` ```
val source: OfflineMessageSource = bot.buildMessageSource { val source: OfflineMessageSource = bot.buildMessageSource {

View File

@ -6,7 +6,7 @@ import kotlin.math.pow
buildscript { buildscript {
repositories { repositories {
mavenLocal() 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") maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
jcenter() jcenter()
google() google()
@ -51,7 +51,7 @@ allprojects {
version = Versions.Mirai.version version = Versions.Mirai.version
repositories { 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") maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
jcenter() jcenter()
google() google()

View File

@ -24,6 +24,8 @@ object Versions {
const val dokka = "0.10.1" const val dokka = "0.10.1"
} }
const val jcekt = "1.0.0"
object Android { object Android {
const val androidGradlePlugin = "3.5.3" const val androidGradlePlugin = "3.5.3"
} }

View File

@ -51,6 +51,7 @@ kotlin {
api(kotlin("stdlib", Versions.Kotlin.stdlib)) api(kotlin("stdlib", Versions.Kotlin.stdlib))
api(kotlinx("serialization-runtime-common", Versions.Kotlin.serialization)) api(kotlinx("serialization-runtime-common", Versions.Kotlin.serialization))
api(kotlinx("serialization-protobuf-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("org.jetbrains.kotlinx:atomicfu:${Versions.Kotlin.atomicFU}")
api(kotlinx("io", Versions.Kotlin.io)) api(kotlinx("io", Versions.Kotlin.io))
api(kotlinx("coroutines-io", Versions.Kotlin.coroutinesIo)) api(kotlinx("coroutines-io", Versions.Kotlin.coroutinesIo))
@ -86,6 +87,7 @@ kotlin {
dependencies { dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
// api(kotlinx("coroutines-debug", "1.3.5")) // api(kotlinx("coroutines-debug", "1.3.5"))
api("moe.him188:jcekt:${Versions.jcekt}")
api(kotlinx("serialization-runtime", Versions.Kotlin.serialization)) api(kotlinx("serialization-runtime", Versions.Kotlin.serialization))
//api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization)) //api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization))

View File

@ -15,7 +15,6 @@ import io.ktor.client.HttpClient
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.MultiPartFormDataContent import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData import io.ktor.client.request.forms.formData
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.io.ByteReadChannel 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.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.encodeToString import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.collections.asSequence import kotlin.collections.asSequence
import kotlin.contracts.ExperimentalContracts 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) { override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) {
@ -351,10 +349,6 @@ internal abstract class QQAndroidBotBase constructor(
return sequence return sequence
} }
override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult {
TODO("not implemented")
}
@Suppress("RemoveExplicitTypeArguments") // false positive @Suppress("RemoveExplicitTypeArguments") // false positive
override suspend fun recall(source: MessageSource) { override suspend fun recall(source: MessageSource) {
check(source is MessageSourceInternal) check(source is MessageSourceInternal)
@ -375,7 +369,7 @@ internal abstract class QQAndroidBotBase constructor(
else -> error("stub") else -> error("stub")
} }
if (this.id != source.fromId) { if (this.id != source.fromId) {
group.checkBotPermissionOperator() group.checkBotPermission(MemberPermission.ADMINISTRATOR)
} }
MessageRecallEvent.GroupRecall( MessageRecallEvent.GroupRecall(
this, this,
@ -679,8 +673,8 @@ internal abstract class QQAndroidBotBase constructor(
response.proto.uint32UpIp.zip(response.proto.uint32UpPort), response.proto.uint32UpIp.zip(response.proto.uint32UpPort),
response.proto.msgSig, response.proto.msgSig,
MiraiPlatformUtils.md5(body), MiraiPlatformUtils.md5(body),
body.toReadPacket(), @Suppress("INVISIBLE_REFERENCE")
body.size.toLong().and(0xFFFF_FFFF), // don't use toLongUnsigned: Overload resolution ambiguity net.mamoe.mirai.utils.internal.asReusableInput0(body), // don't use toLongUnsigned: Overload resolution ambiguity
"group long message", "group long message",
27 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<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel()
}
/** /**
* 获取 获取群公告 所需的 bkn 参数 * 获取 获取群公告 所需的 bkn 参数
* */ * */

View File

@ -87,6 +87,10 @@ internal class FriendImpl(
@JvmSynthetic @JvmSynthetic
@OptIn(MiraiInternalAPI::class, ExperimentalStdlibApi::class, ExperimentalTime::class) @OptIn(MiraiInternalAPI::class, ExperimentalStdlibApi::class, ExperimentalTime::class)
override suspend fun uploadImage(image: ExternalImage): Image = try { 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) { if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
} }
@ -96,14 +100,15 @@ internal class FriendImpl(
srcUin = bot.id.toInt(), srcUin = bot.id.toInt(),
dstUin = id.toInt(), dstUin = id.toInt(),
fileId = 0, fileId = 0,
fileMd5 = image.md5, fileMd5 = @Suppress("INVISIBLE_MEMBER") image.md5,
fileSize = image.inputSize.toInt(), fileSize = @Suppress("INVISIBLE_MEMBER")
fileName = image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName, image.input.size.toInt(),
fileName = @Suppress("INVISIBLE_MEMBER") image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName,
imgOriginal = 1 imgOriginal = 1
) )
).sendAndExpect<LongConn.OffPicUp.Response>() ).sendAndExpect<LongConn.OffPicUp.Response>()
@Suppress("UNCHECKED_CAST", "DEPRECATION") // bug @Suppress("UNCHECKED_CAST", "DEPRECATION", "INVISIBLE_MEMBER")
return when (response) { return when (response) {
is LongConn.OffPicUp.Response.FileExists -> net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId) is LongConn.OffPicUp.Response.FileExists -> net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId)
.also { .also {
@ -111,7 +116,7 @@ internal class FriendImpl(
} }
is LongConn.OffPicUp.Response.RequireUpload -> { is LongConn.OffPicUp.Response.RequireUpload -> {
bot.network.logger.verbose { bot.network.logger.verbose {
"[Http] Uploading friend image, size=${image.inputSize.sizeToString()}" "[Http] Uploading friend image, size=${image.input.size.sizeToString()}"
} }
val time = measureTime { val time = measureTime {
@ -120,13 +125,12 @@ internal class FriendImpl(
bot.id, bot.id,
null, null,
imageInput = image.input, imageInput = image.input,
inputSize = image.inputSize,
uKeyHex = response.uKey.toUHexString("") uKeyHex = response.uKey.toUHexString("")
) )
} }
bot.network.logger.verbose { 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 { } finally {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
(image.input as? Closeable)?.close() (image.input as? Closeable)?.close()
} }
} }

View File

@ -109,7 +109,8 @@ internal class GroupImpl(
override var name: String override var name: String
get() = _name get() = _name
set(newValue) { set(newValue) {
checkBotPermissionOperator()
checkBotPermission(MemberPermission.ADMINISTRATOR)
if (_name != newValue) { if (_name != newValue) {
val oldValue = _name val oldValue = _name
_name = newValue _name = newValue
@ -131,7 +132,7 @@ internal class GroupImpl(
override var entranceAnnouncement: String override var entranceAnnouncement: String
get() = _announcement get() = _announcement
set(newValue) { set(newValue) {
checkBotPermissionOperator() checkBotPermission(MemberPermission.ADMINISTRATOR)
if (_announcement != newValue) { if (_announcement != newValue) {
val oldValue = _announcement val oldValue = _announcement
_announcement = newValue _announcement = newValue
@ -152,7 +153,7 @@ internal class GroupImpl(
override var isAllowMemberInvite: Boolean override var isAllowMemberInvite: Boolean
get() = _allowMemberInvite get() = _allowMemberInvite
set(newValue) { set(newValue) {
checkBotPermissionOperator() checkBotPermission(MemberPermission.ADMINISTRATOR)
if (_allowMemberInvite != newValue) { if (_allowMemberInvite != newValue) {
val oldValue = _allowMemberInvite val oldValue = _allowMemberInvite
_allowMemberInvite = newValue _allowMemberInvite = newValue
@ -186,7 +187,8 @@ internal class GroupImpl(
override var isConfessTalkEnabled: Boolean override var isConfessTalkEnabled: Boolean
get() = _confessTalk get() = _confessTalk
set(newValue) { set(newValue) {
checkBotPermissionOperator()
checkBotPermission(MemberPermission.ADMINISTRATOR)
if (_confessTalk != newValue) { if (_confessTalk != newValue) {
val oldValue = _confessTalk val oldValue = _confessTalk
_confessTalk = newValue _confessTalk = newValue
@ -207,7 +209,8 @@ internal class GroupImpl(
override var isMuteAll: Boolean override var isMuteAll: Boolean
get() = _muteAll get() = _muteAll
set(newValue) { set(newValue) {
checkBotPermissionOperator()
checkBotPermission(MemberPermission.ADMINISTRATOR)
if (_muteAll != newValue) { if (_muteAll != newValue) {
val oldValue = _muteAll val oldValue = _muteAll
_muteAll = newValue _muteAll = newValue
@ -403,6 +406,10 @@ internal class GroupImpl(
@OptIn(ExperimentalTime::class) @OptIn(ExperimentalTime::class)
@JvmSynthetic @JvmSynthetic
override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage = try { 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) { if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
} }
@ -412,7 +419,7 @@ internal class GroupImpl(
uin = bot.id, uin = bot.id,
groupCode = id, groupCode = id,
md5 = image.md5, md5 = image.md5,
size = image.inputSize size = image.input.size.toInt()
).sendAndExpect() ).sendAndExpect()
@Suppress("UNCHECKED_CAST") // bug @Suppress("UNCHECKED_CAST") // bug
@ -432,7 +439,7 @@ internal class GroupImpl(
bot, bot,
response.uploadIpList.zip(response.uploadPortList), response.uploadIpList.zip(response.uploadPortList),
response.uKey, response.uKey,
image, image.input,
kind = "group image", kind = "group image",
commandId = 2 commandId = 2
) )

View File

@ -105,7 +105,7 @@ internal class MemberImpl constructor(
get() = _nameCard get() = _nameCard
set(newValue) { set(newValue) {
if (id != bot.id) { if (id != bot.id) {
group.checkBotPermissionOperator() group.checkBotPermission(MemberPermission.ADMINISTRATOR)
} }
if (_nameCard != newValue) { if (_nameCard != newValue) {
val oldValue = _nameCard val oldValue = _nameCard
@ -118,7 +118,7 @@ internal class MemberImpl constructor(
newValue newValue
).sendWithoutExpect() ).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}" } check(response.success) { "kick failed: ${response.ret}" }
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
group.members.delegate.removeIf { it.id == this@MemberImpl.id } group.members.delegate.removeIf { it.id == this@MemberImpl.id }
MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast() MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast()
} }

View File

@ -59,17 +59,17 @@ internal fun Contact.logMessageSent(message: Message) {
} }
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal fun ContactMessage.logMessageReceived() { internal fun MessageEvent.logMessageReceived() {
when (this) { when (this) {
is GroupMessage -> bot.logger.verbose { is GroupMessageEvent -> bot.logger.verbose {
"[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(${sender.id}) -> ${message.toString() "[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(${sender.id}) -> ${message.toString()
.singleLine()}" .singleLine()}"
} }
is TempMessage -> bot.logger.verbose { is TempMessageEvent -> bot.logger.verbose {
"[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(Temp ${sender.id}) -> ${message.toString() "[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(Temp ${sender.id}) -> ${message.toString()
.singleLine()}" .singleLine()}"
} }
is FriendMessage -> bot.logger.verbose { is FriendMessageEvent -> bot.logger.verbose {
"${sender.nick.singleLine()}(${sender.id}) -> ${message.toString().singleLine()}" "${sender.nick.singleLine()}(${sender.id}) -> ${message.toString().singleLine()}"
} }
} }

View File

@ -358,10 +358,10 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
="" a_actionData="" url=""/></msg> ="" a_actionData="" url=""/></msg>
*/ */
/** /**
* [JsonMessage] * json?
*/ */
1 -> @Suppress("DEPRECATION_ERROR") 1 -> @Suppress("DEPRECATION_ERROR")
list.add(JsonMessage(content)) list.add(ServiceMessage(1, content))
/** /**
* [LongMessage], [ForwardMessage] * [LongMessage], [ForwardMessage]
*/ */
@ -381,7 +381,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
else -> { else -> {
if (element.richMsg.serviceId == 60 || content.startsWith("<?")) { if (element.richMsg.serviceId == 60 || content.startsWith("<?")) {
@Suppress("DEPRECATION_ERROR") // bin comp @Suppress("DEPRECATION_ERROR") // bin comp
list.add(XmlMessage(element.richMsg.serviceId, content)) list.add(ServiceMessage(element.richMsg.serviceId, content))
} else list.add(ServiceMessage(element.richMsg.serviceId, content)) } else list.add(ServiceMessage(element.richMsg.serviceId, content))
} }
} }

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.AtomicRef
@ -20,7 +22,7 @@ import kotlinx.io.core.use
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.message.ContactMessage import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.UnsupportedSMSLoginException import net.mamoe.mirai.network.UnsupportedSMSLoginException
import net.mamoe.mirai.network.WrongPasswordException import net.mamoe.mirai.network.WrongPasswordException
@ -44,6 +46,7 @@ import net.mamoe.mirai.qqandroid.utils.io.useBytes
import net.mamoe.mirai.qqandroid.utils.retryCatching import net.mamoe.mirai.qqandroid.utils.retryCatching
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmField
import kotlin.jvm.Volatile import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@ -204,6 +207,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
private val _pendingEnabled = atomic(true) private val _pendingEnabled = atomic(true)
internal val pendingEnabled get() = _pendingEnabled.value internal val pendingEnabled get() = _pendingEnabled.value
@JvmField
@Volatile @Volatile
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? = internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? =
LockFreeLinkedList() LockFreeLinkedList()
@ -491,7 +495,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
is Packet.NoLog -> { is Packet.NoLog -> {
// nothing to do // nothing to do
} }
is ContactMessage -> packet.logMessageReceived() is MessageEvent -> packet.logMessageReceived()
is Event -> bot.logger.verbose { "Event: ${packet.toString().singleLine()}" } is Event -> bot.logger.verbose { "Event: ${packet.toString().singleLine()}" }
else -> logger.verbose { "Packet: ${packet.toString().singleLine()}" } else -> logger.verbose { "Packet: ${packet.toString().singleLine()}" }
} }

View File

@ -77,6 +77,10 @@ internal open class QQAndroidClient(
val device: DeviceInfo = SystemDeviceInfo(context), val device: DeviceInfo = SystemDeviceInfo(context),
bot: QQAndroidBot bot: QQAndroidBot
) { ) {
@Suppress("INVISIBLE_MEMBER")
val subAppId: Long
get() = bot.configuration.protocol.id
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList() internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
val keys: Map<String, ByteArray> by lazy { val keys: Map<String, ByteArray> by lazy {
@ -368,7 +372,7 @@ internal class WLoginSigInfo(
val deviceToken: ByteArray val deviceToken: ByteArray
) { ) {
override fun toString(): String { 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()})"
} }
} }

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.qqandroid.network.highway package net.mamoe.mirai.qqandroid.network.highway
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@ -20,22 +22,24 @@ import io.ktor.utils.io.ByteWriteChannel
import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.io.InputStream
import kotlinx.io.core.Input
import kotlinx.io.core.discardExact import kotlinx.io.core.discardExact
import kotlinx.io.core.readAvailable
import kotlinx.io.core.use import kotlinx.io.core.use
import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead 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.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.withUse 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.coroutines.EmptyCoroutineContext
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@ -47,8 +51,7 @@ internal suspend fun HttpClient.postImage(
htcmd: String, htcmd: String,
uin: Long, uin: Long,
groupcode: Long?, groupcode: Long?,
imageInput: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor imageInput: ReusableInput,
inputSize: Long,
uKeyHex: String uKeyHex: String
): Boolean = post<HttpStatusCode> { ): Boolean = post<HttpStatusCode> {
url { url {
@ -63,7 +66,7 @@ internal suspend fun HttpClient.postImage(
parameters["term"] = "pc" parameters["term"] = "pc"
parameters["ver"] = "5603" parameters["ver"] = "5603"
parameters["filesize"] = inputSize.toString() parameters["filesize"] = imageInput.size.toString()
parameters["range"] = 0.toString() parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex parameters["ukey"] = uKeyHex
@ -72,63 +75,46 @@ internal suspend fun HttpClient.postImage(
body = object : OutgoingContent.WriteChannelContent() { body = object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType = ContentType.Image.Any override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize override val contentLength: Long = imageInput.size
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override suspend fun writeTo(channel: ByteWriteChannel) { override suspend fun writeTo(channel: ByteWriteChannel) {
ByteArrayPool.useInstance { buffer: ByteArray -> imageInput.writeTo(channel)
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}")
}
}
} }
} }
} == HttpStatusCode.OK } == HttpStatusCode.OK
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal object HighwayHelper { internal object HighwayHelper {
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
suspend fun uploadImageToServers( suspend fun uploadImageToServers(
bot: QQAndroidBot, bot: QQAndroidBot,
servers: List<Pair<Int, Int>>, servers: List<Pair<Int, Int>>,
uKey: ByteArray, uKey: ByteArray,
image: ExternalImage, image: ReusableInput,
kind: String, kind: String,
commandId: Int 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) @OptIn(ExperimentalTime::class)
suspend fun uploadImageToServers( suspend fun uploadImageToServers(
bot: QQAndroidBot, bot: QQAndroidBot,
servers: List<Pair<Int, Int>>, servers: List<Pair<Int, Int>>,
uKey: ByteArray, uKey: ByteArray,
md5: ByteArray, md5: ByteArray,
input: Any, input: ReusableInput,
inputSize: Long,
kind: String, kind: String,
commandId: Int commandId: Int
) = servers.retryWithServers( ) = servers.retryWithServers(
(inputSize * 1000 / 1024 / 10).coerceAtLeast(5000), (input.size * 1000 / 1024 / 10).coerceAtLeast(5000),
onFail = { onFail = {
throw IllegalStateException("cannot upload $kind, failed on all servers.", it) throw IllegalStateException("cannot upload $kind, failed on all servers.", it)
} }
) { ip, port -> ) { ip, port ->
bot.network.logger.verbose { 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 { val time = measureTime {
@ -137,7 +123,6 @@ internal object HighwayHelper {
serverIp = ip, serverIp = ip,
serverPort = port, serverPort = port,
imageInput = input, imageInput = input,
inputSize = inputSize.toInt(),
fileMd5 = md5, fileMd5 = md5,
ticket = uKey, ticket = uKey,
commandId = commandId commandId = commandId
@ -145,22 +130,21 @@ internal object HighwayHelper {
} }
bot.network.logger.verbose { 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) @OptIn(InternalCoroutinesApi::class)
internal suspend fun uploadImage( internal suspend fun uploadImage(
client: QQAndroidClient, client: QQAndroidClient,
serverIp: String, serverIp: String,
serverPort: Int, serverPort: Int,
ticket: ByteArray, ticket: ByteArray,
imageInput: Any, imageInput: ReusableInput,
inputSize: Int,
fileMd5: ByteArray, fileMd5: ByteArray,
commandId: Int // group=2, friend=1 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(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" }
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.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" } // require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
@ -177,13 +161,14 @@ internal object HighwayHelper {
socket.use { socket.use {
createImageDataPacketSequence( createImageDataPacketSequence(
client = client, client = client,
appId = client.subAppId.toInt(),
command = "PicUp.DataUp", command = "PicUp.DataUp",
commandId = commandId, commandId = commandId,
ticket = ticket, ticket = ticket,
data = imageInput, data = imageInput,
dataSize = inputSize,
fileMd5 = fileMd5 fileMd5 = fileMd5
).collect { ).withUse {
flow.collect {
socket.send(it) 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 //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
@ -197,6 +182,7 @@ internal object HighwayHelper {
} }
} }
} }
}
} }

View File

@ -11,12 +11,7 @@
package net.mamoe.mirai.qqandroid.network.highway 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.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import kotlinx.serialization.InternalSerializationApi 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.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils 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.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.utils.MiraiInternalAPI 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) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal fun createImageDataPacketSequence( internal fun createImageDataPacketSequence(
// RequestDataTrans // RequestDataTrans
client: QQAndroidClient, client: QQAndroidClient,
command: String, command: String,
appId: Int = 537062845, appId: Int,
dataFlag: Int = 4096, dataFlag: Int = 4096,
commandId: Int, commandId: Int,
localId: Int = 2052, localId: Int = 2052,
ticket: ByteArray, ticket: ByteArray,
data: ReusableInput,
data: Any,
dataSize: Int,
fileMd5: ByteArray, fileMd5: ByteArray,
sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE
): Flow<ByteReadPacket> { ): ChunkedFlowSession<ByteReadPacket> {
ByteArrayPool.checkBufferSize(sizePerPacket) 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(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) { val session: ChunkedFlowSession<ChunkedInput> = data.chunkedFlow(sizePerPacket)
is ByteReadPacket -> data.chunkedFlow(sizePerPacket)
is Input -> data.chunkedFlow(sizePerPacket)
is ByteReadChannel -> data.chunkedFlow(sizePerPacket)
is InputStream -> data.chunkedFlow(sizePerPacket)
else -> error("unreachable code")
}
var offset = 0L var offset = 0L
return flow.map { chunkedInput -> return session.map { chunkedInput ->
buildPacket { buildPacket {
val head = CSDataHighwayHead.ReqDataHighwayHead( val head = CSDataHighwayHead.ReqDataHighwayHead(
msgBasehead = CSDataHighwayHead.DataHighwayHead( msgBasehead = CSDataHighwayHead.DataHighwayHead(
@ -82,7 +70,7 @@ internal fun createImageDataPacketSequence(
// cacheAddr = 812157193, // cacheAddr = 812157193,
datalength = chunkedInput.bufferSize, datalength = chunkedInput.bufferSize,
dataoffset = offset, dataoffset = offset,
filesize = dataSize.toLong(), filesize = data.size,
serviceticket = ticket, serviceticket = ticket,
md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize), md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
fileMd5 = fileMd5, fileMd5 = fileMd5,

View File

@ -10,9 +10,9 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -1,8 +1,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -1,9 +1,9 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable 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.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
internal class OnlinePushPack { internal class OnlinePushPack {

View File

@ -10,10 +10,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY 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.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")

View File

@ -1,8 +1,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -10,9 +10,9 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable 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.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
private val EMPTY_MAP = mapOf<String, String>() private val EMPTY_MAP = mapOf<String, String>()

View File

@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
@Serializable @Serializable

View File

@ -197,6 +197,7 @@ internal object KnownPacketFactories {
readString(readInt() - 4)// uinAccount readString(readInt() - 4)// uinAccount
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { data -> ByteArrayPool.useInstance(this.remaining.toInt()) { data ->
val size = this.readAvailable(data) val size = this.readAvailable(data)
@ -219,6 +220,7 @@ internal object KnownPacketFactories {
it as IncomingPacket<T> it as IncomingPacket<T>
if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) { if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.network.pendingIncomingPackets?.addLast(it.also { bot.network.pendingIncomingPackets?.addLast(it.also {
it.consumer = consumer it.consumer = consumer
it.flag2 = flag2 it.flag2 = flag2
@ -384,6 +386,7 @@ internal object KnownPacketFactories {
} }
0 -> { 0 -> {
val data = if (bot.client.loginState == 0) { val data = if (bot.client.loginState == 0) {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { byteArrayBuffer -> ByteArrayPool.useInstance(this.remaining.toInt()) { byteArrayBuffer ->
val size = (this.remaining - 1).toInt() val size = (this.remaining - 1).toInt()
this.readFully(byteArrayBuffer, 0, size) this.readFully(byteArrayBuffer, 0, size)

View File

@ -83,7 +83,7 @@ internal fun BytePacketBuilder.t18(
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal fun BytePacketBuilder.t106( internal fun BytePacketBuilder.t106(
appId: Long = 16L, appId: Long = 16L,
subAppId: Long = 537062845L, subAppId: Long,
appClientVersion: Int = 0, appClientVersion: Int = 0,
uin: Long, uin: Long,
n5_always_1: Int = 1, n5_always_1: Int = 1,
@ -159,7 +159,7 @@ internal fun BytePacketBuilder.t116(
internal fun BytePacketBuilder.t100( internal fun BytePacketBuilder.t100(
appId: Long = 16, appId: Long = 16,
subAppId: Long = 537062845, subAppId: Long,
appClientVersion: Int appClientVersion: Int
) { ) {
writeShort(0x100) writeShort(0x100)

View File

@ -100,7 +100,7 @@ internal class TroopManagement {
serviceType = 7, serviceType = 7,
result = 0, result = 0,
bodybuffer = Oidb0x88d.ReqBody( bodybuffer = Oidb0x88d.ReqBody(
appid = 537062845, appid = client.subAppId.toInt(),
stzreqgroupinfo = listOf( stzreqgroupinfo = listOf(
Oidb0x88d.ReqGroupInfo( Oidb0x88d.ReqGroupInfo(
stgroupinfo = Oidb0x88d.GroupInfo( stgroupinfo = Oidb0x88d.GroupInfo(

View File

@ -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.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory 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.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.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
import kotlin.random.Random import kotlin.random.Random
@ -27,9 +28,6 @@ internal fun getRandomString(length: Int): String =
private val defaultRanges: Array<CharRange> = arrayOf('a'..'z', 'A'..'Z', '0'..'9') private val defaultRanges: Array<CharRange> = 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 = internal fun getRandomString(length: Int, vararg charRanges: CharRange): String =
String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() }) String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() })
@ -41,7 +39,7 @@ internal class ImgStore {
uin: Long, uin: Long,
groupCode: Long, groupCode: Long,
md5: ByteArray, md5: ByteArray,
size: Long, size: Int,
picWidth: Int = 0, // not orthodox picWidth: Int = 0, // not orthodox
picHeight: Int = 0, // not orthodox picHeight: Int = 0, // not orthodox
picType: Int = 1000, picType: Int = 1000,
@ -63,7 +61,7 @@ internal class ImgStore {
groupCode = groupCode, groupCode = groupCode,
srcUin = uin, srcUin = uin,
fileMd5 = md5, fileMd5 = md5,
fileSize = size, fileSize = size.toLongUnsigned(),
fileId = fileId, fileId = fileId,
fileName = filename, fileName = filename,
picWidth = picWidth, picWidth = picWidth,

View File

@ -30,8 +30,8 @@ import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.TempMessage import net.mamoe.mirai.message.TempMessageEvent
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PttMessage import net.mamoe.mirai.message.data.PttMessage
import net.mamoe.mirai.message.data.Voice import net.mamoe.mirai.message.data.Voice
@ -228,6 +228,7 @@ internal class MessageSvc {
// 新群 // 新群
val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.groups.delegate.addLast(newGroup) bot.groups.delegate.addLast(newGroup)
return@mapNotNull BotJoinGroupEvent(newGroup) return@mapNotNull BotJoinGroupEvent(newGroup)
} else { } else {
@ -237,6 +238,7 @@ internal class MessageSvc {
return@mapNotNull null return@mapNotNull null
} }
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo()) return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) }) .also { group.members.delegate.addLast(it) })
} }
@ -250,6 +252,7 @@ internal class MessageSvc {
if (group.members.contains(msg.msgHead.authUin)) { if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null return@mapNotNull null
} }
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo()) return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) }) .also { group.members.delegate.addLast(it) })
} }
@ -276,7 +279,7 @@ internal class MessageSvc {
friend.lastMessageSequence.loop { instant -> friend.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) { if (msg.msgHead.msgSeq > instant) {
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) { if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull FriendMessage( return@mapNotNull FriendMessageEvent(
friend, friend,
msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true), msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true),
msg.msgHead.msgTime msg.msgHead.msgTime
@ -307,7 +310,7 @@ internal class MessageSvc {
member.lastMessageSequence.loop { instant -> member.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) { if (msg.msgHead.msgSeq > instant) {
if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) { if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull TempMessage( return@mapNotNull TempMessageEvent(
member, member,
msg.toMessageChain( msg.toMessageChain(
bot, bot,

View File

@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 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.event.events.*
import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.getGroupOrNull 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.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.* import net.mamoe.mirai.qqandroid.contact.*
import net.mamoe.mirai.qqandroid.message.contextualBugReportException 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) { val sender = if (anonymous != null) {
group.newAnonymous(anonymous.anonNick.encodeToString()) group.newAnonymous(anonymous.anonNick.encodeToString())
} else { } else {
@ -108,12 +109,12 @@ internal class OnlinePush {
} }
val flags = extraInfo?.flags ?: 0 val flags = extraInfo?.flags ?: 0
return GroupMessage( return GroupMessageEvent(
senderName = name.also { senderName = name.also {
if (it != sender.nameCard) { if (it != sender.nameCard) {
val origin = sender._nameCard val origin = sender._nameCard
sender._nameCard = name sender._nameCard = name
MemberCardChangeEvent(origin, name, sender, sender).broadcast() // 不知道operator MemberCardChangeEvent(origin, name, sender).broadcast()
} }
}, },
sender = sender, sender = sender,
@ -562,7 +563,7 @@ internal class OnlinePush {
if (new == old) return@mapNotNull null if (new == old) return@mapNotNull null
member._nameCard = new member._nameCard = new
return@mapNotNull MemberCardChangeEvent(old, new, member, null) return@mapNotNull MemberCardChangeEvent(old, new, member)
} }
2 -> { 2 -> {
if (info.value.singleOrNull()?.toInt() != 0) { if (info.value.singleOrNull()?.toInt() != 0) {

View File

@ -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.readUniPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeJceStruct import net.mamoe.mirai.qqandroid.utils.io.serialization.writeJceStruct
import net.mamoe.mirai.qqandroid.utils.toByteArray import net.mamoe.mirai.qqandroid.utils.toByteArray
import net.mamoe.mirai.utils.SinceMirai
internal class ProfileService { internal class ProfileService {
@SinceMirai("0.37.0")
object GroupMngReq : OutgoingPacketFactory<GroupMngReq.GroupMngReqResponse>("ProfileService.GroupMngReq") { object GroupMngReq : OutgoingPacketFactory<GroupMngReq.GroupMngReqResponse>("ProfileService.GroupMngReq") {
data class GroupMngReqResponse(val errorCode: Int, val errorMessage: String) : Packet data class GroupMngReqResponse(val errorCode: Int, val errorMessage: String) : Packet

View File

@ -70,6 +70,7 @@ internal class ConfigPushSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse? { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse? {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { buffer -> ByteArrayPool.useInstance(this.remaining.toInt()) { buffer ->
val length = this.readAvailable(buffer) val length = this.readAvailable(buffer)

View File

@ -25,7 +25,7 @@ internal class Heartbeat {
operator fun invoke( operator fun invoke(
client: QQAndroidClient client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, 0, key = NO_ENCRYPT) { ): OutgoingPacket = buildLoginOutgoingPacket(client, 0, key = NO_ENCRYPT) {
writeSsoPacket(client, 537062845, commandName, sequenceId = it) { writeSsoPacket(client, client.subAppId, commandName, sequenceId = it) {
} }
} }

View File

@ -10,6 +10,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.Packet import net.mamoe.mirai.qqandroid.network.Packet
@ -88,8 +89,6 @@ internal class StatSvc {
override fun toString(): String = "Response(StatSvc.register)" override fun toString(): String = "Response(StatSvc.register)"
} }
private const val subAppId = 537062845L
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
operator fun invoke( operator fun invoke(
client: QQAndroidClient, client: QQAndroidClient,
@ -101,7 +100,7 @@ internal class StatSvc {
key = client.wLoginSigInfo.d2Key key = client.wLoginSigInfo.d2Key
) { sequenceId -> ) { sequenceId ->
writeSsoPacket( writeSsoPacket(
client, subAppId = subAppId, commandName = commandName, client, subAppId = client.subAppId, commandName = commandName,
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
) { ) {
writeJceStruct( writeJceStruct(
@ -153,7 +152,7 @@ internal class StatSvc {
var44.strVendorName = ROMUtil.getRomName(); var44.strVendorName = ROMUtil.getRomName();
var44.strVendorOSName = ROMUtil.getRomVersion(20); var44.strVendorOSName = ROMUtil.getRomVersion(20);
*/ */
bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump( bytes_0x769_reqbody = ProtoBuf.dump(
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody( Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
rpt_config_list = listOf( rpt_config_list = listOf(
Oidb0x769.ConfigSeq( Oidb0x769.ConfigSeq(

View File

@ -32,8 +32,6 @@ internal class WtLogin {
@Suppress("FunctionName") @Suppress("FunctionName")
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal object Login : OutgoingPacketFactory<Login.LoginPacketResponse>("wtlogin.login") { internal object Login : OutgoingPacketFactory<Login.LoginPacketResponse>("wtlogin.login") {
private const val subAppId = 537062845L
/** /**
* 提交验证码 * 提交验证码
*/ */
@ -42,7 +40,7 @@ internal class WtLogin {
client: QQAndroidClient, client: QQAndroidClient,
ticket: String ticket: String
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): 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) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand writeShort(2) // subCommand
writeShort(4) // count of TLVs writeShort(4) // count of TLVs
@ -59,7 +57,7 @@ internal class WtLogin {
captchaSign: ByteArray, captchaSign: ByteArray,
captchaAnswer: String captchaAnswer: String
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): 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) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand writeShort(2) // subCommand
writeShort(4) // count of TLVs writeShort(4) // count of TLVs
@ -78,7 +76,7 @@ internal class WtLogin {
client: QQAndroidClient, client: QQAndroidClient,
t402: ByteArray t402: ByteArray
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): 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) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(20) // subCommand writeShort(20) // subCommand
writeShort(4) // count of TLVs, probably ignored by server? writeShort(4) // count of TLVs, probably ignored by server?
@ -100,7 +98,7 @@ internal class WtLogin {
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket( writeSsoPacket(
client, client,
subAppId, client.subAppId,
commandName, commandName,
sequenceId = sequenceId, sequenceId = sequenceId,
unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00" unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00"
@ -126,13 +124,12 @@ internal class WtLogin {
*/ */
object SubCommand9 { object SubCommand9 {
private const val appId = 16L private const val appId = 16L
private const val subAppId = 537062845L
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
operator fun invoke( operator fun invoke(
client: QQAndroidClient client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> ): 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) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(9) // subCommand writeShort(9) // subCommand
writeShort(17) // count of TLVs, probably ignored by server? writeShort(17) // count of TLVs, probably ignored by server?
@ -142,7 +139,7 @@ internal class WtLogin {
t1(client.uin, client.device.ipAddress) t1(client.uin, client.device.ipAddress)
t106( t106(
appId, appId,
subAppId /* maybe 1*/, client.subAppId /* maybe 1*/,
client.appClientVersion, client.appClientVersion,
client.uin, client.uin,
1, 1,
@ -166,7 +163,7 @@ internal class WtLogin {
if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L) if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
*/ */
t116(client.miscBitMap, client.subSigMap) t116(client.miscBitMap, client.subSigMap)
t100(appId, subAppId, client.appClientVersion) t100(appId, client.subAppId, client.appClientVersion)
t107(0) t107(0)
// t108(byteArrayOf()) // t108(byteArrayOf())

View File

@ -1,41 +1,9 @@
package net.mamoe.mirai.qqandroid.utils package net.mamoe.mirai.qqandroid.utils
import kotlinx.io.pool.DefaultPool
import kotlinx.io.pool.ObjectPool import kotlinx.io.pool.ObjectPool
/** /**
* 缓存 [ByteArray] 实例的 [ObjectPool] * 缓存 [ByteArray] 实例的 [ObjectPool]
*/ */
internal object ByteArrayPool : DefaultPool<ByteArray>(256) { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
/** internal typealias ByteArrayPool = net.mamoe.mirai.utils.internal.ByteArrayPool
* 每一个 [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 <R> 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)
}
}
}

View File

@ -27,6 +27,7 @@ import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
internal inline fun <R> ByteReadPacket.useBytes( internal inline fun <R> ByteReadPacket.useBytes(
n: Int = remaining.toInt(),//not that safe but adequate n: Int = remaining.toInt(),//not that safe but adequate
block: (data: ByteArray, length: Int) -> R block: (data: ByteArray, length: Int) -> R

View File

@ -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 <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output)
fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T
}

View File

@ -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<JceId>(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<Int>() {
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 <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
serializer.descriptor.kind == StructureKind.MAP -> {
try {
val entries = (value as Map<*, *>).entries
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
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<Byte>).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<Int>() {
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<Byte>
// 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 <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): 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<Any?, T, Any?>).typeParams.isNotEmpty()
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).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<Any?, Any?, T, *>)
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 <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) 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(".")
?: "<unnamed class>"
}
fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket {
val encoder = BytePacketBuilder()
val dumper = JceEncoder(encoder)
dumper.encode(serializer, obj)
return encoder.build()
}
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
return dumpAsPacket(serializer, value).readBytes()
}
/**
* 注意 close [packet]!!
*/
fun <T> load(
deserializer: DeserializationStrategy<T>,
packet: ByteReadPacket,
length: Int = packet.remaining.toInt()
): T {
return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer)
}
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
return bytes.toReadPacket().use {
val decoder = JceDecoder(JceInput(it))
decoder.decode(deserializer)
}
}
}
internal inline fun <R> 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()
}
}

View File

@ -23,10 +23,13 @@ import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType import kotlinx.serialization.protobuf.ProtoType
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint import net.mamoe.mirai.qqandroid.utils.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint
internal typealias ProtoDesc = Pair<Int, ProtoNumberType> internal typealias ProtoDesc = Pair<Int, ProtoNumberType>
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<JceId>(index)?.id
internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefault: Boolean = false): ProtoDesc { internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefault: Boolean = false): ProtoDesc {
val idx = getSerialId(desc, index) ?: (if (zeroBasedDefault) index else index + 1) val idx = getSerialId(desc, index) ?: (if (zeroBasedDefault) index else index + 1)
val format = desc.findAnnotation<ProtoType>(index)?.type val format = desc.findAnnotation<ProtoType>(index)?.type

View File

@ -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`

View File

@ -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<JceTag>() {
override val updateMode: UpdateMode
get() = UpdateMode.BANNED
override fun SerialDescriptor.getTag(index: Int): JceTag {
val annotations = this.getElementAnnotations(index)
val id = annotations.filterIsInstance<JceId>().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
}
}

View File

@ -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 <R> useHead(crossinline block: (JceHead) -> R): R {
return currentHead.let(block).also { prepareNextHead() }
}
/**
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则返回 `null`
*/
inline fun <R> skipToHeadAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? {
return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() }
}
/**
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常
*/
inline fun <R : Any> skipToHeadAndUseIfPossibleOrFail(
tag: Int,
crossinline message: () -> String = { "tag not found: $tag" },
crossinline block: (JceHead) -> R
): R {
return checkNotNull<R>(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()
}
}

View File

@ -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 <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output) {
output.writePacket(JceOld.byCharSet(this.charset).dumpAsPacket(serializer, ojb))
}
override fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T {
return JceDecoder(
JceInput(
input,
charset
), context
).decodeSerializableValue(deserializer)
}
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
return buildPacket { dumpTo(serializer, value, this) }.readBytes()
}
override fun <T> load(deserializer: DeserializationStrategy<T>, 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
}
}

View File

@ -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")
}
}
}
}

View File

@ -16,15 +16,14 @@ import kotlinx.io.core.*
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialDescriptor import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerializationStrategy 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.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 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.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.qqandroid.utils.io.JceStruct import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact 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.qqandroid.utils.read
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -34,25 +33,21 @@ internal fun <T : JceStruct> ByteArray.loadWithUniPacket(
): T = this.read { readUniPacket(deserializer, name) } ): T = this.read { readUniPacket(deserializer, name) }
internal fun <T : JceStruct> ByteArray.loadAs( internal fun <T : JceStruct> ByteArray.loadAs(
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>
c: JceCharset = JceCharset.UTF8 ): T = this.read { Jce.UTF_8.load(deserializer, this) }
): T = this.read { Jce.byCharSet(c).load(deserializer, this) }
internal fun <T : JceStruct> BytePacketBuilder.writeJceStruct( internal fun <T : JceStruct> BytePacketBuilder.writeJceStruct(
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>,
struct: T, struct: T
charset: JceCharset = JceCharset.UTF8
) { ) {
Jce.byCharSet(charset).dumpTo(serializer, struct, this) Jce.UTF_8.dumpTo(serializer, struct, this)
} }
internal fun <T : JceStruct> ByteReadPacket.readJceStruct( internal fun <T : JceStruct> ByteReadPacket.readJceStruct(
serializer: DeserializationStrategy<T>, serializer: DeserializationStrategy<T>,
charset: JceCharset = JceCharset.UTF8,
length: Int = this.remaining.toInt() length: Int = this.remaining.toInt()
): T { ): T {
@OptIn(MiraiInternalAPI::class) return Jce.UTF_8.load(serializer, this.readPacketExact(length))
return Jce.byCharSet(charset).load(serializer, this.readPacketExact(length))
} }
/** /**
@ -103,9 +98,8 @@ private fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String
} }
internal fun <T : JceStruct> T.toByteArray( internal fun <T : JceStruct> T.toByteArray(
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>
c: JceCharset = JceCharset.UTF8 ): ByteArray = Jce.UTF_8.dump(serializer, this)
): ByteArray = Jce.byCharSet(c).dump(serializer, this)
internal fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) { internal fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
this.writeFully(v.toByteArray(serializer)) this.writeFully(v.toByteArray(serializer))
@ -140,19 +134,12 @@ internal fun <T : JceStruct> jceRequestSBuffer(
name: String, name: String,
serializer: SerializationStrategy<T>, serializer: SerializationStrategy<T>,
jceStruct: T jceStruct: T
): ByteArray = jceRequestSBuffer(name, serializer, jceStruct, JceCharset.UTF8)
internal fun <T : JceStruct> jceRequestSBuffer(
name: String,
serializer: SerializationStrategy<T>,
jceStruct: T,
charset: JceCharset
): ByteArray { ): ByteArray {
return RequestDataVersion3( return RequestDataVersion3(
mapOf( mapOf(
name to JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0 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) private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A)

View File

@ -42,7 +42,6 @@ actual abstract class Group : Contact(), CoroutineScope {
/** /**
* 群设置 * 群设置
*/ */
@SinceMirai("0.30.0")
actual abstract val settings: GroupSettings actual abstract val settings: GroupSettings
/** /**

View File

@ -15,10 +15,8 @@ package net.mamoe.mirai
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent
import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent
@ -29,7 +27,6 @@ import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -48,6 +45,8 @@ suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
* *
* @see Contact 联系人 * @see Contact 联系人
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close]) * @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
*
* @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式.
*/ */
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class) @OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
@ -56,6 +55,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/** /**
* 复制一份此时的 [Bot] 实例列表. * 复制一份此时的 [Bot] 实例列表.
*/ */
@PlannedRemoval("1.2.0")
@Deprecated("use botInstances instead", replaceWith = ReplaceWith("botInstances")) @Deprecated("use botInstances instead", replaceWith = ReplaceWith("botInstances"))
@JvmStatic @JvmStatic
val instances: List<WeakRef<Bot>> val instances: List<WeakRef<Bot>>
@ -64,7 +64,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/** /**
* 复制一份此时的 [Bot] 实例列表. * 复制一份此时的 [Bot] 实例列表.
*/ */
@SinceMirai("0.39.1")
@JvmStatic @JvmStatic
val botInstances: List<Bot> val botInstances: List<Bot>
get() = BotImpl.instances.asSequence().mapNotNull { it.get() }.toList() get() = BotImpl.instances.asSequence().mapNotNull { it.get() }.toList()
@ -72,7 +71,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/** /**
* 遍历每一个 [Bot] 实例 * 遍历每一个 [Bot] 实例
*/ */
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
/** /**
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
@ -89,20 +88,14 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
*/ */
abstract val context: Context abstract val context: Context
@PlannedRemoval("1.0.0")
@Deprecated("use id instead", replaceWith = ReplaceWith("id"))
abstract val uin: Long
/** /**
* QQ 号码. 实际类型为 uint * QQ 号码. 实际类型为 uint
*/ */
@SinceMirai("0.32.0")
abstract override val id: Long abstract override val id: Long
/** /**
* 昵称 * 昵称
*/ */
@SinceMirai("0.33.1")
abstract val nick: String abstract val nick: String
/** /**
@ -113,7 +106,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
// region contacts // region contacts
/** /**
* [QQ.id] [Bot.uin] 相同的 [_lowLevelNewFriend] 实例 * [User.id] [Bot.id] 相同的 [_lowLevelNewFriend] 实例
*/ */
abstract val selfQQ: Friend abstract val selfQQ: Friend
@ -179,6 +172,8 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/** /**
* 获取图片下载链接 * 获取图片下载链接
*
* @see Image.queryUrl [Image] 的扩展函数
*/ */
@JvmSynthetic @JvmSynthetic
abstract suspend fun queryImageUrl(image: Image): String abstract suspend fun queryImageUrl(image: Image): String
@ -193,7 +188,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param targetUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算 * @param targetUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.39.0")
abstract fun constructMessageSource( abstract fun constructMessageSource(
kind: OfflineMessageSource.Kind, kind: OfflineMessageSource.Kind,
fromUin: Long, targetUin: Long, fromUin: Long, targetUin: Long,
@ -201,35 +195,12 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
originalMessage: MessageChain originalMessage: MessageChain
): OfflineMessageSource ): 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 好友验证的事件对象 * @param event 好友验证的事件对象
*/ */
@SinceMirai("0.35.0")
@JvmSynthetic @JvmSynthetic
abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent)
@ -239,7 +210,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 好友验证的事件对象 * @param event 好友验证的事件对象
* @param blackList 拒绝后是否拉入黑名单 * @param blackList 拒绝后是否拉入黑名单
*/ */
@SinceMirai("0.35.0")
@JvmSynthetic @JvmSynthetic
abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false) abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false)
@ -248,7 +218,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* *
* @param event 加群验证的事件对象 * @param event 加群验证的事件对象
*/ */
@SinceMirai("0.35.0")
@JvmSynthetic @JvmSynthetic
abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent) abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent)
@ -258,7 +227,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 加群验证的事件对象 * @param event 加群验证的事件对象
* @param blackList 拒绝后是否拉入黑名单 * @param blackList 拒绝后是否拉入黑名单
*/ */
@SinceMirai("0.35.0")
@JvmSynthetic @JvmSynthetic
abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false) abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
@ -268,7 +236,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 加群验证的事件对象 * @param event 加群验证的事件对象
* @param blackList 忽略后是否拉入黑名单 * @param blackList 忽略后是否拉入黑名单
*/ */
@SinceMirai("0.35.0")
@JvmSynthetic @JvmSynthetic
abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false) abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
@ -277,7 +244,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* *
* @param event 邀请入群的事件对象 * @param event 邀请入群的事件对象
*/ */
@SinceMirai("0.39.4")
@JvmSynthetic @JvmSynthetic
abstract suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) abstract suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
@ -287,7 +253,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 邀请入群的事件对象 * @param event 邀请入群的事件对象
*/ */
@JvmSynthetic @JvmSynthetic
@SinceMirai("0.39.4")
abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
// endregion // endregion
@ -313,19 +278,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
*/ */
@MiraiInternalAPI @MiraiInternalAPI
abstract val network: BotNetworkHandler 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)
} }
/** /**

View File

@ -14,15 +14,16 @@ package net.mamoe.mirai
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.Context
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* 构造 [Bot] 的工厂. * 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式.
* *
* 在协议模块中有各自的实现. * `mirai-core-qqandroid`: `QQAndroid`
* - `mirai-core-timpc`: `TIMPC` *
* - `mirai-core-qqandroid`: `QQAndroid` * JVM, 请查看 `BotFactoryJvm`
*/ */
interface BotFactory { expect interface BotFactory {
/** /**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例 * 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/ */
@ -49,6 +50,7 @@ interface BotFactory {
/** /**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例 * 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/ */
@JvmSynthetic
inline fun BotFactory.Bot( inline fun BotFactory.Bot(
context: Context, context: Context,
qq: Long, qq: Long,
@ -59,6 +61,7 @@ inline fun BotFactory.Bot(
/** /**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例 * 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/ */
@JvmSynthetic
inline fun BotFactory.Bot( inline fun BotFactory.Bot(
context: Context, context: Context,
qq: Long, qq: Long,

View File

@ -24,6 +24,8 @@ import net.mamoe.mirai.network.closeAndJoin
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.retryCatching import net.mamoe.mirai.utils.internal.retryCatching
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
/* /*
* 泛型 N 不需要向外(接口)暴露. * 泛型 N 不需要向外(接口)暴露.
@ -46,10 +48,6 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
override val context: Context by context.unsafeWeakRef() 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) } final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) }
init { init {
@ -60,7 +58,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@PublishedApi @PublishedApi
internal val instances: LockFreeLinkedList<WeakRef<Bot>> = LockFreeLinkedList() internal val instances: LockFreeLinkedList<WeakRef<Bot>> = LockFreeLinkedList()
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach { fun forEachInstance(block: (Bot) -> Unit) = instances.forEach {
it.get()?.let(block) it.get()?.let(block)
} }
@ -90,6 +88,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@Throws(LoginFailedException::class) // only @Throws(LoginFailedException::class) // only
protected abstract suspend fun relogin(cause: Throwable?) protected abstract suspend fun relogin(cause: Throwable?)
@OptIn(ExperimentalTime::class)
@Suppress("unused") @Suppress("unused")
private val offlineListener: Listener<BotOfflineEvent> = private val offlineListener: Listener<BotOfflineEvent> =
this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event -> this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
@ -107,6 +106,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
} }
bot.logger.info { "Connection dropped by server or lost, retrying login" } bot.logger.info { "Connection dropped by server or lost, retrying login" }
val time = measureTime {
tailrec suspend fun reconnect() { tailrec suspend fun reconnect() {
retryCatching<Unit>(configuration.reconnectionRetryTimes, retryCatching<Unit>(configuration.reconnectionRetryTimes,
except = LoginFailedException::class) { tryCount, _ -> except = LoginFailedException::class) { tryCount, _ ->
@ -120,7 +120,6 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class) @OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause) relogin((event as? BotOfflineEvent.Dropped)?.cause)
} }
logger.info { "Reconnected successfully" }
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast() BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
return return
}.getOrElse { }.getOrElse {
@ -136,9 +135,11 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
} }
reconnect() reconnect()
} }
reconnect() reconnect()
} }
logger.info { "Reconnected successfully in ${time.inMilliseconds} ms" }
}
is BotOfflineEvent.Active -> { is BotOfflineEvent.Active -> {
val msg = if (event.cause == null) { val msg = if (event.cause == null) {
"" ""

View File

@ -32,11 +32,11 @@ import kotlin.jvm.JvmSynthetic
/** /**
* 联系. 虽然叫做联系人, 但他的子类有 [用户][User], [][Group]. * 联系对象, 即可以与 [Bot] 互动的对象. 包含 [用户][User], [][Group].
*/ */
abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot { abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot {
/** /**
* 这个联系所属 [Bot]. * 这个联系对象所属 [Bot].
*/ */
@WeakRefProperty @WeakRefProperty
abstract val bot: Bot abstract val bot: Bot
@ -44,10 +44,7 @@ abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot
/** /**
* 可以是 QQ 号码或者群号码. * 可以是 QQ 号码或者群号码.
* *
* 对于 [QQ], `uin` `id` 是相同的意思. * @see User.id
* 对于 [Group], `groupCode` `id` 是相同的意思.
*
* @see QQ.id
* @see Group.id * @see Group.id
*/ */
abstract override val id: Long abstract override val id: Long

View File

@ -11,8 +11,9 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.LockFreeLinkedList
import kotlin.jvm.JvmName import net.mamoe.mirai.utils.asSequence
import kotlin.jvm.JvmField
/** /**
@ -20,9 +21,8 @@ import kotlin.jvm.JvmName
* *
* @see ContactList.asSequence * @see ContactList.asSequence
*/ */
@OptIn(MiraiInternalAPI::class)
@Suppress("unused") @Suppress("unused")
class ContactList<C : Contact>(@MiraiInternalAPI("Implementation may change in future release") val delegate: LockFreeLinkedList<C>) : class ContactList<C : Contact> internal constructor(@JvmField internal val delegate: LockFreeLinkedList<C>) :
Iterable<C> { Iterable<C> {
operator fun get(id: Long): C = operator fun get(id: Long): C =
delegate.asSequence().firstOrNull { it.id == id } ?: throw NoSuchElementException("Contact id $id") delegate.asSequence().firstOrNull { it.id == id } ?: throw NoSuchElementException("Contact id $id")
@ -41,31 +41,6 @@ class ContactList<C : Contact>(@MiraiInternalAPI("Implementation may change in f
override fun iterator(): Iterator<C> { override fun iterator(): Iterator<C> {
return this.delegate.asSequence().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<C : Contact>(@MiraiInternalAPI("Implementation may change in f
* ``` * ```
*/ */
val ContactList<*>.idContentString: String 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 2
) + "]" ) + "]"
@MiraiInternalAPI internal operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
forEach { if (it.id == id) return it } forEach { if (it.id == id) return it }
throw NoSuchElementException("No such contact: $id") throw NoSuchElementException("No such contact: $id")
} }
@MiraiInternalAPI internal fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
forEach { if (it.id == id) return it } forEach { if (it.id == id) return it }
return null 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 <C : Contact> LockFreeLinkedList<C>.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 <C : Contact> LockFreeLinkedList<C>.filteringGetOrNull(filter: (C) -> Boolean): C? =
this.asSequence().firstOrNull(filter)
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toList from stdlib", level = DeprecationLevel.HIDDEN)
fun <E : Contact> ContactList<E>.toList(): List<E> = toMutableList()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toMutableList from stdlib", level = DeprecationLevel.HIDDEN)
@OptIn(MiraiInternalAPI::class)
fun <E : Contact> ContactList<E>.toMutableList(): MutableList<E> = this.delegate.toMutableList()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toSet from stdlib", level = DeprecationLevel.HIDDEN)
fun <E : Contact> ContactList<E>.toSet(): Set<E> = toMutableSet()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toMutableSet from stdlib", level = DeprecationLevel.HIDDEN)
@OptIn(MiraiInternalAPI::class)
fun <E : Contact> ContactList<E>.toMutableSet(): MutableSet<E> = this.delegate.toMutableSet()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.asSequence from stdlib", level = DeprecationLevel.HIDDEN)
@OptIn(MiraiInternalAPI::class)
fun <E : Contact> ContactList<E>.asSequence(): Sequence<E> = this.delegate.asSequence()

View File

@ -10,7 +10,6 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.SinceMirai
/** /**
* 拥有 [id] 的对象. * 拥有 [id] 的对象.
@ -19,7 +18,6 @@ import net.mamoe.mirai.utils.SinceMirai
* @see Contact * @see Contact
* @see Bot * @see Bot
*/ */
@SinceMirai("0.39.0")
interface ContactOrBot { interface ContactOrBot {
/** /**
* QQ 号或群号. * QQ 号或群号.

View File

@ -12,14 +12,12 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.SinceMirai
/** /**
* 发送消息时消息过长抛出的异常. * 发送消息时消息过长抛出的异常.
* *
* @see Contact.sendMessage * @see Contact.sendMessage
*/ */
@SinceMirai("0.32.0")
class MessageTooLargeException( class MessageTooLargeException(
val target: Contact, val target: Contact,
/** /**
@ -38,7 +36,6 @@ class MessageTooLargeException(
* *
* @see Group.sendMessage * @see Group.sendMessage
*/ */
@SinceMirai("0.33.0")
class BotIsBeingMutedException( class BotIsBeingMutedException(
val target: Group val target: Group
) : RuntimeException("bot is being muted, remaining ${target.botMuteRemaining} seconds") ) : RuntimeException("bot is being muted, remaining ${target.botMuteRemaining} seconds")

View File

@ -16,21 +16,23 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent 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.MessageReceipt
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.toMessage import net.mamoe.mirai.message.data.toMessage
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
/** /**
* 好友 对象. * 代表一位好友.
* 注意: 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot].
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
* *
* 对于同一个 [Bot] 任何一个人的 [Friend] 实例都是单一的. * 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot].
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取. * 对于同一个 [Bot], 任何一个人的 [Friend] 实例都是单一的.
* [Friend] 无法通过任何方式直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
*
* @see FriendMessageEvent
*/ */
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
abstract class Friend : QQ(), CoroutineScope { abstract class Friend : User(), CoroutineScope {
/** /**
* 请求头像下载链接 * 请求头像下载链接
*/ */

View File

@ -47,7 +47,6 @@ abstract class Group : Contact(), CoroutineScope {
/** /**
* 群设置 * 群设置
*/ */
@SinceMirai("0.30.0")
abstract val settings: GroupSettings abstract val settings: GroupSettings
/** /**
@ -80,7 +79,6 @@ abstract class Group : Contact(), CoroutineScope {
* 机器人在这个群里的权限 * 机器人在这个群里的权限
* *
* @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限 * @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限
* @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator]
* *
* @see BotGroupPermissionChangeEvent 机器人群员修改 * @see BotGroupPermissionChangeEvent 机器人群员修改
*/ */
@ -123,7 +121,6 @@ abstract class Group : Contact(), CoroutineScope {
* @return 退出成功时 true; 已经退出时 false * @return 退出成功时 true; 已经退出时 false
*/ */
@JvmSynthetic @JvmSynthetic
@SinceMirai("0.37.0")
abstract suspend fun quit(): Boolean abstract suspend fun quit(): Boolean
/** /**
@ -197,7 +194,6 @@ abstract class Group : Contact(), CoroutineScope {
@Suppress("FunctionName") @Suppress("FunctionName")
@JvmName("quit") @JvmName("quit")
@JavaFriendlyAPI @JavaFriendlyAPI
@SinceMirai("0.39.4")
fun __quitBlockingForJava__(): Boolean = runBlocking { quit() } fun __quitBlockingForJava__(): Boolean = runBlocking { quit() }
} }
@ -206,7 +202,6 @@ abstract class Group : Contact(), CoroutineScope {
* *
* @see Group.settings 获取群设置 * @see Group.settings 获取群设置
*/ */
@SinceMirai("0.30.0")
interface GroupSettings { interface GroupSettings {
/** /**
* 入群公告, 没有时为空字符串. * 入群公告, 没有时为空字符串.

View File

@ -27,4 +27,4 @@ expect abstract class ContactJavaFriendlyAPI internal constructor()
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
@MiraiInternalAPI @MiraiInternalAPI
@JavaFriendlyAPI @JavaFriendlyAPI
expect abstract class MemberJavaFriendlyAPI internal constructor() : QQ // 将来会改为 User expect abstract class MemberJavaFriendlyAPI internal constructor() : User

View File

@ -19,19 +19,19 @@ import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.toMessage import net.mamoe.mirai.message.data.toMessage
import net.mamoe.mirai.utils.MiraiInternalAPI 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 net.mamoe.mirai.utils.WeakRefProperty
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
/** /**
* 群成员. * 代表一位群成员.
* *
* 群成员可能也是好友, 但他们在对象类型上不同. * 群成员可能也是好友, 但他们在对象类型上不同.
* 群成员可以通过 [asFriend] 得到相关好友对象. * 群成员可以通过 [asFriend] 得到相关好友对象.
*
* ## 与好友相关的操作
* [Member.isFriend] 判断此成员是否为好友
*/ */
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
@OptIn(MiraiInternalAPI::class, JavaFriendlyAPI::class) @OptIn(MiraiInternalAPI::class, JavaFriendlyAPI::class)
@ -95,12 +95,16 @@ abstract class Member : MemberJavaFriendlyAPI() {
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
* @return 机器人无权限时返回 `false` * @return 机器人无权限时返回 `false`
* *
* @see Member.isMuted 判断此成员是否正处于禁言状态中
* @see unmute 取消禁言此成员
*
* @see Int.minutesToSeconds * @see Int.minutesToSeconds
* @see Int.hoursToSeconds * @see Int.hoursToSeconds
* @see Int.daysToSeconds * @see Int.daysToSeconds
* *
* @see MemberMuteEvent 成员被禁言事件 * @see MemberMuteEvent 成员被禁言事件
* @throws PermissionDeniedException 无权限修改时 *
* @throws PermissionDeniedException 无权限修改时抛出
*/ */
@JvmSynthetic @JvmSynthetic
abstract suspend fun mute(durationSeconds: Int) abstract suspend fun mute(durationSeconds: Int)
@ -110,8 +114,11 @@ abstract class Member : MemberJavaFriendlyAPI() {
* *
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
* *
* @see MemberUnmuteEvent 成员被取消禁言事件. * @see Member.isMuted 判断此成员是否正处于禁言状态中
* @throws PermissionDeniedException 无权限修改时 *
* @see MemberUnmuteEvent 成员被取消禁言事件
*
* @throws PermissionDeniedException 无权限修改时抛出
*/ */
@JvmSynthetic @JvmSynthetic
abstract suspend fun unmute() abstract suspend fun unmute()
@ -164,26 +171,22 @@ abstract class Member : MemberJavaFriendlyAPI() {
* *
* @throws IllegalStateException 当此成员不是好友时抛出 * @throws IllegalStateException 当此成员不是好友时抛出
*/ */
@SinceMirai("0.39.0")
fun Member.asFriend(): Friend = this.bot.getFriendOrNull(this.id) ?: error("$this is not a friend") 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) fun Member.asFriendOrNull(): Friend? = this.bot.getFriendOrNull(this.id)
/** /**
* 判断此成员是否为好友 * 判断此成员是否为好友
*/ */
@SinceMirai("0.39.0")
inline val Member.isFriend: Boolean inline val Member.isFriend: Boolean
get() = this.bot.friends.contains(this.id) get() = this.bot.friends.contains(this.id)
/** /**
* 如果此成员是好友, 则执行 [block] 并返回其返回值. 否则返回 `null` * 如果此成员是好友, 则执行 [block] 并返回其返回值. 否则返回 `null`
*/ */
@SinceMirai("0.39.0")
inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? { inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? {
return this.asFriendOrNull()?.let(block) return this.asFriendOrNull()?.let(block)
} }
@ -191,7 +194,7 @@ inline fun <R> 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 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] * 否则返回 [Member.nick]
*/ */
@SinceMirai("0.39.0")
val User.nameCardOrNick: String val User.nameCardOrNick: String
get() = when (this) { get() = when (this) {
is Member -> this.nameCardOrNick is Member -> this.nameCardOrNick
else -> this.nick 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 val Member.isMuted: Boolean
get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt() get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt()

View File

@ -7,17 +7,24 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * 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 package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.SinceMirai import kotlin.internal.InlineOnly
/** /**
* 群成员的权限. * 群成员的权限.
* *
* 可通过 [compareTo] 判断是否有更高的权限. * 可通过 [compareTo] 判断是否有更高的权限.
*
* @see isOwner 判断权限是否为群主
* @see isOperator 判断权限是否为管理员或群主
*
* @see Member.isOwner [Member] 的扩展函数, 判断此成员是否为群主
* @see Member.isOperator [Member] 的扩展函数, 判断此成员是否为管理员或群主
* @see Member.isAdministrator [Member] 的扩展函数, 判断此成员是否为管理员
*/ */
enum class MemberPermission : Comparable<MemberPermission> { enum class MemberPermission : Comparable<MemberPermission> {
/** /**
@ -38,7 +45,6 @@ enum class MemberPermission : Comparable<MemberPermission> {
/** /**
* 权限等级. [OWNER] 2, [ADMINISTRATOR] 1, [MEMBER] 0 * 权限等级. [OWNER] 2, [ADMINISTRATOR] 1, [MEMBER] 0
*/ */
@SinceMirai("0.32.0")
val level: Int val level: Int
get() = ordinal get() = ordinal
} }
@ -46,16 +52,19 @@ enum class MemberPermission : Comparable<MemberPermission> {
/** /**
* 判断权限是否为群主 * 判断权限是否为群主
*/ */
@InlineOnly
inline fun MemberPermission.isOwner(): Boolean = this == MemberPermission.OWNER inline fun MemberPermission.isOwner(): Boolean = this == MemberPermission.OWNER
/** /**
* 判断权限是否为管理员 * 判断权限是否为管理员
*/ */
@InlineOnly
inline fun MemberPermission.isAdministrator(): Boolean = this == MemberPermission.ADMINISTRATOR inline fun MemberPermission.isAdministrator(): Boolean = this == MemberPermission.ADMINISTRATOR
/** /**
* 判断权限是否为管理员或群主 * 判断权限是否为管理员或群主
*/ */
@InlineOnly
inline fun MemberPermission.isOperator(): Boolean = isAdministrator() || isOwner() inline fun MemberPermission.isOperator(): Boolean = isAdministrator() || isOwner()
@ -85,7 +94,7 @@ class PermissionDeniedException : IllegalStateException {
} }
/** /**
* 要求 [Bot] 在这个群里的权限[required], 否则抛出异常 [PermissionDeniedException] * 要求 [Bot] 在这个群里的权限至少[required], 否则抛出异常 [PermissionDeniedException]
* *
* @throws PermissionDeniedException * @throws PermissionDeniedException
*/ */
@ -95,7 +104,7 @@ inline fun Group.checkBotPermission(
"Permission denied: required $required, got actual $botPermission for $bot in group $id" "Permission denied: required $required, got actual $botPermission for $bot in group $id"
} }
) { ) {
if (botPermission != required) { if (botPermission < required) {
throw PermissionDeniedException(lazyMessage()) throw PermissionDeniedException(lazyMessage())
} }
} }
@ -105,12 +114,9 @@ inline fun Group.checkBotPermission(
* *
* @throws PermissionDeniedException * @throws PermissionDeniedException
*/ */
@Deprecated("use checkBotPermission", ReplaceWith("checkBotPermission(MemberPermission.ADMINISTRATOR)"))
inline fun Group.checkBotPermissionOperator( inline fun Group.checkBotPermissionOperator(
crossinline lazyMessage: () -> String = { crossinline lazyMessage: () -> String = {
"Permission denied: required ${MemberPermission.ADMINISTRATOR} or ${MemberPermission.OWNER}, got actual $botPermission for $bot in group $id" "Permission denied: required ${MemberPermission.ADMINISTRATOR} or ${MemberPermission.OWNER}, got actual $botPermission for $bot in group $id"
} }
) { ) = checkBotPermission(MemberPermission.ADMINISTRATOR, lazyMessage)
if (!botPermission.isOperator()) {
throw PermissionDeniedException(lazyMessage())
}
}

View File

@ -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<QQ>
}

View File

@ -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)"
}
}

View File

@ -4,14 +4,12 @@ package net.mamoe.mirai.data
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
/** /**
* 群统计信息 * 群统计信息
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.28.0")
@Serializable @Serializable
data class GroupActiveData( data class GroupActiveData(

View File

@ -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<String>
) : List<String> by list {
override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ")
}

View File

@ -11,9 +11,7 @@
package net.mamoe.mirai.data 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), SECRET(0),
MALE(1), MALE(1),
FEMALE(2) FEMALE(2)
} }*/

View File

@ -70,7 +70,7 @@ interface Event {
abstract class AbstractEvent : Event { abstract class AbstractEvent : Event {
@Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "PropertyName") @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "PropertyName")
@get:JvmSynthetic // so Java user won't see it @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 final override val DoNotImplementThisClassButExtendAbstractEvent: Nothing
get() = throw Error("Shouldn't be reached") get() = throw Error("Shouldn't be reached")

View File

@ -17,13 +17,11 @@ package net.mamoe.mirai.event
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.internal.* import net.mamoe.mirai.event.internal.*
import net.mamoe.mirai.message.ContactMessage import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.TempMessage import net.mamoe.mirai.message.TempMessageEvent
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.PlannedRemoval
import kotlin.js.JsName
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -33,7 +31,7 @@ import kotlin.jvm.JvmSynthetic
* 消息事件的处理器. * 消息事件的处理器.
* *
* : * :
* 接受者 T [ContactMessage] * 接受者 T [MessageEvent]
* 参数 String 转为字符串了的消息 ([Message.toString]) * 参数 String 转为字符串了的消息 ([Message.toString])
*/ */
typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
@ -49,7 +47,7 @@ typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
* @see subscribeFriendMessages * @see subscribeFriendMessages
*/ */
@MessageDsl @MessageDsl
open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>( open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, RR>(
/** /**
* 用于 [MessageListener] 无返回值的替代. * 用于 [MessageListener] 无返回值的替代.
*/ */
@ -233,7 +231,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
/** 如果是这个人发的消息. 消息目前只会是群消息 */ /** 如果是这个人发的消息. 消息目前只会是群消息 */
@MessageDsl @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 @MessageDsl
@ -249,36 +247,37 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
/** 如果是好友发来的消息 */ /** 如果是好友发来的消息 */
@MessageDsl @MessageDsl
fun sentByFriend(onEvent: MessageListener<FriendMessage, R>): Ret = fun sentByFriend(onEvent: MessageListener<FriendMessageEvent, R>): Ret =
content({ this is FriendMessage }) { onEvent(this as FriendMessage, it) } content({ this is FriendMessageEvent }) { onEvent(this as FriendMessageEvent, it) }
/** 如果是好友发来的消息 */ /** 如果是好友发来的消息 */
@MessageDsl @MessageDsl
fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage } fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessageEvent }
/** 如果是好友发来的消息 */ /** 如果是群临时会话消息 */
@MessageDsl @MessageDsl
fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessage } fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessageEvent }
/** 如果是管理员或群主发的消息 */ /** 如果是管理员或群主发的消息 */
@MessageDsl @MessageDsl
fun sentByOperator(): ListeningFilter = content { this is GroupMessage && sender.permission.isOperator() } fun sentByOperator(): ListeningFilter = content { this is GroupMessageEvent && sender.permission.isOperator() }
/** 如果是管理员发的消息 */ /** 如果是管理员发的消息 */
@MessageDsl @MessageDsl
fun sentByAdministrator(): ListeningFilter = content { this is GroupMessage && sender.permission.isAdministrator() } fun sentByAdministrator(): ListeningFilter =
content { this is GroupMessageEvent && sender.permission.isAdministrator() }
/** 如果是群主发的消息 */ /** 如果是群主发的消息 */
@MessageDsl @MessageDsl
fun sentByOwner(): ListeningFilter = content { this is GroupMessage && sender.isOwner() } fun sentByOwner(): ListeningFilter = content { this is GroupMessageEvent && sender.isOwner() }
/** 如果是来自这个群的消息 */ /** 如果是来自这个群的消息 */
@MessageDsl @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 @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] */ /** [消息内容][Message.contentToString]包含目标为 [Bot] 的 [At] */
@MessageDsl @MessageDsl
@ -440,79 +439,6 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
} }
return stub 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<M, R>): 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<M, R>): 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<M, R>
): 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<M, R>
): 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<M, R>): Ret =
content({ (this as? GroupMessage)?.senderName == name }, onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentByOperator(onEvent: MessageListener<M, R>): Ret =
content({ this is GroupMessage && this.sender.isOperator() }, onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentByAdministrator(onEvent: MessageListener<M, R>): Ret =
content({ this is GroupMessage && this.sender.isAdministrator() }, onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentByOwner(onEvent: MessageListener<M, R>): 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<GroupMessage, R>): Ret =
content({ this is GroupMessage && this.group.id == groupId }) { onEvent(this as GroupMessage, it) }
} }
/** /**

View File

@ -25,7 +25,9 @@ import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.qqandroid.network.Packet 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 net.mamoe.mirai.utils.internal.runBlocking
import kotlin.jvm.JvmField import kotlin.jvm.JvmField
import kotlin.jvm.JvmName 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 data class RequireReconnect(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
} }
@ -131,7 +132,6 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
* 消息内部 id. * 消息内部 id.
* @see MessageSource.id * @see MessageSource.id
*/ */
@SinceMirai("0.39.0")
abstract val messageInternalId: Int abstract val messageInternalId: Int
/** /**
@ -148,7 +148,7 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
override val messageInternalId: Int, override val messageInternalId: Int,
override val messageTime: Int, override val messageTime: Int,
/** /**
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id] * 撤回操作人, 可能为 [Bot.uin] 或好友的 [User.id]
*/ */
val operator: Long val operator: Long
) : MessageRecallEvent(), Packet { ) : MessageRecallEvent(), Packet {
@ -235,20 +235,17 @@ sealed class ImageUploadEvent : BotEvent, BotActiveEvent, AbstractEvent() {
/** /**
* 机器人被踢出群或在其他客户端主动退出一个群. 在事件广播前 [Bot.groups] 就已删除这个群. * 机器人被踢出群或在其他客户端主动退出一个群. 在事件广播前 [Bot.groups] 就已删除这个群.
*/ */
@SinceMirai("0.36.0")
sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent() { sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent() {
abstract val group: Group abstract val group: Group
/** /**
* 机器人主动退出一个群. * 机器人主动退出一个群.
*/ */
@SinceMirai("0.37.0")
data class Active(override val group: Group) : BotLeaveEvent() data class Active(override val group: Group) : BotLeaveEvent()
/** /**
* 机器人被管理员或群主踢出群. 暂不支持获取操作人 * 机器人被管理员或群主踢出群. 暂不支持获取操作人
*/ */
@SinceMirai("0.37.0")
data class Kick(override val group: Group) : BotLeaveEvent() data class Kick(override val group: Group) : BotLeaveEvent()
override val bot: Bot get() = group.bot override val bot: Bot get() = group.bot
@ -321,7 +318,6 @@ data class GroupNameChangeEvent(
/** /**
* 操作人. null 时则是机器人操作 * 操作人. null 时则是机器人操作
*/ */
@SinceMirai("0.37.3")
override val operator: Member? override val operator: Member?
) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent() { ) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent() {
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @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) data class Invite(override val member: Member) : MemberJoinEvent(member)
/** /**
* 成员主动加入群 * 成员主动加入群
*/ */
@SinceMirai("0.36.0")
data class Active(override val member: Member) : MemberJoinEvent(member) data class Active(override val member: Member) : MemberJoinEvent(member)
} }
@ -451,7 +445,6 @@ sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() {
/** /**
* [Bot] 被邀请加入一个群. * [Bot] 被邀请加入一个群.
*/ */
@SinceMirai("0.39.4")
data class BotInvitedJoinGroupRequestEvent( data class BotInvitedJoinGroupRequestEvent(
override val bot: Bot, override val bot: Bot,
/** /**
@ -494,7 +487,6 @@ data class BotInvitedJoinGroupRequestEvent(
/** /**
* 一个账号请求加入群事件, [Bot] 在此群中是管理员或群主. * 一个账号请求加入群事件, [Bot] 在此群中是管理员或群主.
*/ */
@SinceMirai("0.35.0")
data class MemberJoinRequestEvent( data class MemberJoinRequestEvent(
override val bot: Bot, override val bot: Bot,
/** /**
@ -566,17 +558,8 @@ data class MemberCardChangeEvent(
*/ */
val new: String, val new: String,
override val member: Member, override val member: Member
) : GroupMemberEvent, Packet, AbstractEvent()
/**
* 此事件无法确定操作人, 将在未来版本删除
*/
@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
/** /**
* 成员群头衔改动. 一定为群主操作 * 成员群头衔改动. 一定为群主操作
@ -623,6 +606,8 @@ data class MemberPermissionChangeEvent(
/** /**
* 群成员被禁言事件. 被禁言的成员都不可能是机器人本人 * 群成员被禁言事件. 被禁言的成员都不可能是机器人本人
*
* @see BotMuteEvent 机器人被禁言的事件
*/ */
data class MemberMuteEvent( data class MemberMuteEvent(
override val member: Member, override val member: Member,
@ -635,6 +620,8 @@ data class MemberMuteEvent(
/** /**
* 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人 * 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人
*
* @see BotUnmuteEvent 机器人被取消禁言的事件
*/ */
data class MemberUnmuteEvent( data class MemberUnmuteEvent(
override val member: Member, override val member: Member,
@ -655,65 +642,36 @@ data class MemberUnmuteEvent(
/** /**
* 好友昵称改变事件. 目前仅支持解析 (来自 PC 端的修改). * 好友昵称改变事件. 目前仅支持解析 (来自 PC 端的修改).
*/ */
@SinceMirai("0.36.0")
data class FriendRemarkChangeEvent( data class FriendRemarkChangeEvent(
override val bot: Bot, override val bot: Bot,
val friend: Friend, override val friend: Friend,
val newName: String val newName: String
) : BotEvent, Packet, AbstractEvent() { ) : FriendEvent, 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
}
/** /**
* 成功添加了一个新好友的事件 * 成功添加了一个新好友的事件
*/ */
@SinceMirai("0.36.0")
data class FriendAddEvent( data class FriendAddEvent(
/** /**
* 新好友. 已经添加到 [Bot.friends] * 新好友. 已经添加到 [Bot.friends]
*/ */
val friend: Friend override val friend: Friend
) : BotEvent, Packet, AbstractEvent() { ) : FriendEvent, Packet, AbstractEvent() {
override val bot: Bot get() = friend.bot 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( data class FriendDeleteEvent(
val friend: Friend override val friend: Friend
) : BotEvent, Packet, AbstractEvent() { ) : FriendEvent, Packet, AbstractEvent() {
override val bot: Bot get() = friend.bot 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( data class NewFriendRequestEvent(
override val bot: Bot, override val bot: Bot,
/** /**
@ -725,7 +683,7 @@ data class NewFriendRequestEvent(
*/ */
val message: String, val message: String,
/** /**
* 请求人 [QQ.id] * 请求人 [User.id]
*/ */
val fromId: Long, val fromId: Long,
/** /**

View File

@ -15,8 +15,6 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* 有关一个 [Bot] 的事件 * 有关一个 [Bot] 的事件
@ -90,11 +88,4 @@ interface FriendEvent : BotEvent {
val friend: Friend val friend: Friend
override val bot: Bot override val bot: Bot
get() = friend.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
} }

View File

@ -12,7 +12,7 @@ package net.mamoe.mirai.event.internal
import net.mamoe.mirai.event.MessageDsl import net.mamoe.mirai.event.MessageDsl
import net.mamoe.mirai.event.MessageListener import net.mamoe.mirai.event.MessageListener
import net.mamoe.mirai.event.MessageSubscribersBuilder 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 @MessageDsl
internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.content( internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.content(
filter: M.(String) -> Boolean, filter: M.(String) -> Boolean,
onEvent: MessageListener<M, RR> onEvent: MessageListener<M, RR>
): Ret = ): Ret =
subscriber(filter) { onEvent(this, it) } subscriber(filter) { onEvent(this, it) }
internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.endsWithImpl( internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.endsWithImpl(
suffix: String, suffix: String,
removeSuffix: Boolean = true, removeSuffix: Boolean = true,
trim: Boolean = true, trim: Boolean = true,
@ -46,7 +46,7 @@ internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M,
} }
} }
internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.startsWithImpl( internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.startsWithImpl(
prefix: String, prefix: String,
removePrefix: Boolean = true, removePrefix: Boolean = true,
trim: Boolean = true, trim: Boolean = true,
@ -64,7 +64,7 @@ internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M,
} }
} }
internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsAllImpl( internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsAllImpl(
sub: Array<out String>, sub: Array<out String>,
ignoreCase: Boolean = false, ignoreCase: Boolean = false,
trim: Boolean = true trim: Boolean = true
@ -76,7 +76,7 @@ internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M,
content { sub.all { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } content { sub.all { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } }
} }
internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsAnyImpl( internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsAnyImpl(
vararg sub: String, vararg sub: String,
ignoreCase: Boolean = false, ignoreCase: Boolean = false,
trim: Boolean = true trim: Boolean = true
@ -86,7 +86,7 @@ internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M,
content { list.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } content { list.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } }
} else content { sub.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } } else content { sub.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } }
internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.caseImpl( internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.caseImpl(
equals: String, equals: String,
ignoreCase: Boolean = false, ignoreCase: Boolean = false,
trim: Boolean = true trim: Boolean = true
@ -99,7 +99,7 @@ internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M,
} }
} }
internal fun <M : ContactMessage, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsImpl( internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsImpl(
sub: String, sub: String,
ignoreCase: Boolean = false, ignoreCase: Boolean = false,
trim: Boolean = true, trim: Boolean = true,

View File

@ -12,7 +12,6 @@
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.utils.SinceMirai
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -25,12 +24,13 @@ import kotlin.reflect.KClass
* @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值
* *
* @see asyncFromEvent 本函数的异步版本 * @see asyncFromEvent 本函数的异步版本
* @see subscribe 普通地监听一个事件
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
* *
* @throws TimeoutCancellationException 在超时后抛出. * @throws TimeoutCancellationException 在超时后抛出.
* @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常 * @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常
*/ */
@JvmSynthetic @JvmSynthetic
@SinceMirai("0.39.0")
suspend inline fun <reified E : Event, R : Any> syncFromEvent( suspend inline fun <reified E : Event, R : Any> syncFromEvent(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
crossinline mapper: suspend E.(E) -> R? crossinline mapper: suspend E.(E) -> R?
@ -57,10 +57,12 @@ suspend inline fun <reified E : Event, R : Any> syncFromEvent(
* @return 超时返回 `null`, 否则返回 [mapper] 返回的第一个非 `null` . * @return 超时返回 `null`, 否则返回 [mapper] 返回的第一个非 `null` .
* *
* @see asyncFromEvent 本函数的异步版本 * @see asyncFromEvent 本函数的异步版本
* @see subscribe 普通地监听一个事件
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
*
* @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常 * @throws Throwable [mapper] 抛出任何异常时, 本函数会抛出该异常
*/ */
@JvmSynthetic @JvmSynthetic
@SinceMirai("0.39.0")
suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull( suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull(
timeoutMillis: Long, timeoutMillis: Long,
crossinline mapper: suspend E.(E) -> R? crossinline mapper: suspend E.(E) -> R?
@ -80,10 +82,14 @@ suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull(
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param coroutineContext 额外的 [CoroutineContext] * @param coroutineContext 额外的 [CoroutineContext]
* @param mapper 过滤转换器. 返回非 `null` 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * @param mapper 过滤转换器. 返回非 `null` 则代表得到了需要的值. [syncFromEvent] 会返回这个值
*
* @see syncFromEvent
* @see asyncFromEvent
* @see subscribe 普通地监听一个事件
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
*/ */
@JvmSynthetic @JvmSynthetic
@Suppress("DeferredIsResult") @Suppress("DeferredIsResult")
@SinceMirai("0.39.0")
inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull( inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull(
timeoutMillis: Long, timeoutMillis: Long,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
@ -104,10 +110,14 @@ inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull(
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param coroutineContext 额外的 [CoroutineContext] * @param coroutineContext 额外的 [CoroutineContext]
* @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值
*
* @see syncFromEvent
* @see asyncFromEventOrNull
* @see subscribe 普通地监听一个事件
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
*/ */
@JvmSynthetic @JvmSynthetic
@Suppress("DeferredIsResult") @Suppress("DeferredIsResult")
@SinceMirai("0.39.0")
inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEvent( inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEvent(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
@ -132,9 +142,12 @@ internal suspend inline fun <E : Event, R> syncFromEventImpl(
crossinline mapper: suspend E.(E) -> R? crossinline mapper: suspend E.(E) -> R?
): R = suspendCancellableCoroutine { cont -> ): R = suspendCancellableCoroutine { cont ->
coroutineScope.subscribe(eventClass) { coroutineScope.subscribe(eventClass) {
try {
cont.resumeWith(kotlin.runCatching { cont.resumeWith(kotlin.runCatching {
mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING
}) })
} catch (e: Exception) {
}
return@subscribe ListeningStatus.STOPPED return@subscribe ListeningStatus.STOPPED
} }
} }

View File

@ -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 <reified E : Event> 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 <reified E : BotEvent> 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 <E : Event> nextEventImpl(
eventClass: KClass<E>,
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 <E : BotEvent> nextBotEventImpl(
bot: Bot,
eventClass: KClass<E>,
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 <R> 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)
}
}

View File

@ -12,13 +12,12 @@
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.* 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.Message
import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PlainText
import net.mamoe.mirai.message.isContextIdenticalWith import net.mamoe.mirai.message.isContextIdenticalWith
import net.mamoe.mirai.message.nextMessage import net.mamoe.mirai.message.nextMessage
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import kotlin.experimental.ExperimentalTypeInference import kotlin.experimental.ExperimentalTypeInference
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -59,9 +58,8 @@ import kotlin.jvm.JvmSynthetic
* @see subscribeMessages * @see subscribeMessages
* @see nextMessage 挂起协程并等待下一条消息 * @see nextMessage 挂起协程并等待下一条消息
*/ */
@SinceMirai("0.29.0")
@Suppress("unused") @Suppress("unused")
suspend inline fun <reified T : ContactMessage> T.whileSelectMessages( suspend inline fun <reified T : MessageEvent> T.whileSelectMessages(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
filterContext: Boolean = true, filterContext: Boolean = true,
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
@ -72,9 +70,8 @@ suspend inline fun <reified T : ContactMessage> T.whileSelectMessages(
*/ */
@OptIn(ExperimentalTypeInference::class) @OptIn(ExperimentalTypeInference::class)
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.29.0")
@JvmName("selectMessages1") @JvmName("selectMessages1")
suspend inline fun <reified T : ContactMessage> T.selectMessagesUnit( suspend inline fun <reified T : MessageEvent> T.selectMessagesUnit(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
filterContext: Boolean = true, filterContext: Boolean = true,
crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, Unit>.() -> Unit crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, Unit>.() -> Unit
@ -101,10 +98,9 @@ suspend inline fun <reified T : ContactMessage> T.selectMessagesUnit(
* *
* @see nextMessage 挂起协程并等待下一条消息 * @see nextMessage 挂起协程并等待下一条消息
*/ */
@SinceMirai("0.29.0")
@Suppress("unused") // false positive @Suppress("unused") // false positive
// @BuilderInference // https://youtrack.jetbrains.com/issue/KT-37716 // @BuilderInference // https://youtrack.jetbrains.com/issue/KT-37716
suspend inline fun <reified T : ContactMessage, R> T.selectMessages( suspend inline fun <reified T : MessageEvent, R> T.selectMessages(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
filterContext: Boolean = true, filterContext: Boolean = true,
// @BuilderInference // @BuilderInference
@ -120,8 +116,7 @@ suspend inline fun <reified T : ContactMessage, R> T.selectMessages(
* @see MessageSelectBuilderUnit 查看上层 API * @see MessageSelectBuilderUnit 查看上层 API
*/ */
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
@SinceMirai("0.29.0") abstract class MessageSelectBuilder<M : MessageEvent, R> @PublishedApi internal constructor(
abstract class MessageSelectBuilder<M : ContactMessage, R> @PublishedApi internal constructor(
ownerMessagePacket: M, ownerMessagePacket: M,
stub: Any?, stub: Any?,
subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit
@ -185,9 +180,6 @@ abstract class MessageSelectBuilder<M : ContactMessage, R> @PublishedApi interna
@Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
override fun Regex.findingReply(replier: suspend M.(MatchResult) -> Any?) = error("prohibited") 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) @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
override fun String.endsWithReply(replier: suspend M.(String) -> Any?) = error("prohibited") override fun String.endsWithReply(replier: suspend M.(String) -> Any?) = error("prohibited")
@ -238,8 +230,7 @@ abstract class MessageSelectBuilder<M : ContactMessage, R> @PublishedApi interna
* *
* @see MessageSubscribersBuilder 查看上层 API * @see MessageSubscribersBuilder 查看上层 API
*/ */
@SinceMirai("0.29.0") abstract class MessageSelectBuilderUnit<M : MessageEvent, R> @PublishedApi internal constructor(
abstract class MessageSelectBuilderUnit<M : ContactMessage, R> @PublishedApi internal constructor(
private val ownerMessagePacket: M, private val ownerMessagePacket: M,
stub: Any?, stub: Any?,
subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit
@ -455,7 +446,7 @@ class MessageSelectionTimeoutException : RuntimeException()
@JvmSynthetic @JvmSynthetic
@PublishedApi @PublishedApi
internal suspend inline fun <R> withTimeoutOrCoroutineScope( internal suspend inline fun <R> withSilentTimeoutOrCoroutineScope(
timeoutMillis: Long, timeoutMillis: Long,
noinline block: suspend CoroutineScope.() -> R noinline block: suspend CoroutineScope.() -> R
): R { ): R {
@ -483,13 +474,13 @@ internal val ExceptionHandlerIgnoringCancellationException = CoroutineExceptionH
@PublishedApi @PublishedApi
@BuilderInference @BuilderInference
@OptIn(ExperimentalTypeInference::class) @OptIn(ExperimentalTypeInference::class)
internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl( internal suspend inline fun <reified T : MessageEvent, R> T.selectMessagesImpl(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
isUnit: Boolean, isUnit: Boolean,
filterContext: Boolean = true, filterContext: Boolean = true,
@BuilderInference @BuilderInference
crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, R>.() -> Unit crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, R>.() -> Unit
): R = withTimeoutOrCoroutineScope(timeoutMillis) { ): R = withSilentTimeoutOrCoroutineScope(timeoutMillis) {
var deferred: CompletableDeferred<R>? = CompletableDeferred() var deferred: CompletableDeferred<R>? = CompletableDeferred()
coroutineContext[Job]!!.invokeOnCompletion { coroutineContext[Job]!!.invokeOnCompletion {
deferred?.cancel() deferred?.cancel()
@ -509,7 +500,7 @@ internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl
SELECT_MESSAGE_STUB, SELECT_MESSAGE_STUB,
outside outside
) { ) {
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred
override fun default(onEvent: MessageListener<T, R>) { override fun default(onEvent: MessageListener<T, R>) {
defaultListeners += onEvent defaultListeners += onEvent
@ -525,7 +516,7 @@ internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl
SELECT_MESSAGE_STUB, SELECT_MESSAGE_STUB,
outside outside
) { ) {
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred
override fun default(onEvent: MessageListener<T, R>) { override fun default(onEvent: MessageListener<T, R>) {
defaultListeners += onEvent defaultListeners += onEvent
@ -582,11 +573,11 @@ internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl
@Suppress("unused") @Suppress("unused")
@PublishedApi @PublishedApi
internal suspend inline fun <reified T : ContactMessage> T.whileSelectMessagesImpl( internal suspend inline fun <reified T : MessageEvent> T.whileSelectMessagesImpl(
timeoutMillis: Long = -1, timeoutMillis: Long = -1,
filterContext: Boolean = true, filterContext: Boolean = true,
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
) = withTimeoutOrCoroutineScope(timeoutMillis) { ) = withSilentTimeoutOrCoroutineScope(timeoutMillis) {
var deferred: CompletableDeferred<Boolean>? = CompletableDeferred() var deferred: CompletableDeferred<Boolean>? = CompletableDeferred()
coroutineContext[Job]!!.invokeOnCompletion { coroutineContext[Job]!!.invokeOnCompletion {
deferred?.cancel() deferred?.cancel()
@ -605,7 +596,7 @@ internal suspend inline fun <reified T : ContactMessage> T.whileSelectMessagesIm
SELECT_MESSAGE_STUB, SELECT_MESSAGE_STUB,
outside outside
) { ) {
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
override fun obtainCurrentDeferred(): CompletableDeferred<Boolean>? = deferred override fun obtainCurrentDeferred(): CompletableDeferred<Boolean>? = deferred
override fun default(onEvent: MessageListener<T, Boolean>) { override fun default(onEvent: MessageListener<T, Boolean>) {
defaultListeners += onEvent defaultListeners += onEvent

View File

@ -17,18 +17,17 @@ import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.ReceiveChannel
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.ContactMessage import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.TempMessage import net.mamoe.mirai.message.TempMessageEvent
import net.mamoe.mirai.utils.SinceMirai
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder<ContactMessage, Listener<ContactMessage>, Unit, Unit> typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder<MessageEvent, Listener<MessageEvent>, Unit, Unit>
/** /**
* 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. * 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
@ -47,7 +46,7 @@ fun <R> CoroutineScope.subscribeMessages(
} }
return MessagePacketSubscribersBuilder(Unit) return MessagePacketSubscribersBuilder(Unit)
{ filter, messageListener: MessageListener<ContactMessage, Unit> -> { filter, messageListener: MessageListener<MessageEvent, Unit> ->
// subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [messageListener] // subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [messageListener]
// messageListener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块. // messageListener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块.
subscribeAlways(coroutineContext, concurrencyKind) { subscribeAlways(coroutineContext, concurrencyKind) {
@ -59,7 +58,7 @@ fun <R> CoroutineScope.subscribeMessages(
}.run(listeners) }.run(listeners)
} }
typealias GroupMessageSubscribersBuilder = MessageSubscribersBuilder<GroupMessage, Listener<GroupMessage>, Unit, Unit> typealias GroupMessageSubscribersBuilder = MessageSubscribersBuilder<GroupMessageEvent, Listener<GroupMessageEvent>, Unit, Unit>
/** /**
* 订阅来自所有 [Bot] 的所有群消息事件 * 订阅来自所有 [Bot] 的所有群消息事件
@ -84,7 +83,7 @@ fun <R> CoroutineScope.subscribeGroupMessages(
}.run(listeners) }.run(listeners)
} }
typealias FriendMessageSubscribersBuilder = MessageSubscribersBuilder<FriendMessage, Listener<FriendMessage>, Unit, Unit> typealias FriendMessageSubscribersBuilder = MessageSubscribersBuilder<FriendMessageEvent, Listener<FriendMessageEvent>, Unit, Unit>
/** /**
* 订阅来自所有 [Bot] 的所有好友消息事件 * 订阅来自所有 [Bot] 的所有好友消息事件
@ -109,7 +108,7 @@ fun <R> CoroutineScope.subscribeFriendMessages(
}.run(listeners) }.run(listeners)
} }
typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder<TempMessage, Listener<TempMessage>, Unit, Unit> typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder<TempMessageEvent, Listener<TempMessageEvent>, Unit, Unit>
/** /**
* 订阅来自所有 [Bot] 的所有临时会话消息事件 * 订阅来自所有 [Bot] 的所有临时会话消息事件
@ -211,7 +210,6 @@ fun <R> Bot.subscribeFriendMessages(
* *
* @see CoroutineScope.incoming 打开一个指定事件的接收通道 * @see CoroutineScope.incoming 打开一个指定事件的接收通道
*/ */
@SinceMirai("0.35.0")
@OptIn(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
fun <R> Bot.subscribeTempMessages( fun <R> Bot.subscribeTempMessages(
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,

View File

@ -206,7 +206,6 @@ inline fun <reified E : Event> CoroutineScope.subscribe(
* *
* @see CoroutineScope.subscribe * @see CoroutineScope.subscribe
*/ */
@SinceMirai("0.38.0")
fun <E : Event> CoroutineScope.subscribe( fun <E : Event> CoroutineScope.subscribe(
eventClass: KClass<E>, eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
@ -238,7 +237,6 @@ inline fun <reified E : Event> CoroutineScope.subscribeAlways(
/** /**
* @see CoroutineScope.subscribeAlways * @see CoroutineScope.subscribeAlways
*/ */
@SinceMirai("0.38.0")
fun <E : Event> CoroutineScope.subscribeAlways( fun <E : Event> CoroutineScope.subscribeAlways(
eventClass: KClass<E>, eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
@ -271,7 +269,6 @@ inline fun <reified E : Event> CoroutineScope.subscribeOnce(
/** /**
* @see CoroutineScope.subscribeOnce * @see CoroutineScope.subscribeOnce
*/ */
@SinceMirai("0.38.0")
fun <E : Event> CoroutineScope.subscribeOnce( fun <E : Event> CoroutineScope.subscribeOnce(
eventClass: KClass<E>, eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
@ -308,7 +305,6 @@ inline fun <reified E : BotEvent> Bot.subscribe(
* *
* @see Bot.subscribe * @see Bot.subscribe
*/ */
@SinceMirai("0.38.0")
fun <E : BotEvent> Bot.subscribe( fun <E : BotEvent> Bot.subscribe(
eventClass: KClass<E>, eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
@ -345,7 +341,6 @@ inline fun <reified E : BotEvent> Bot.subscribeAlways(
* *
* @see Bot.subscribeAlways * @see Bot.subscribeAlways
*/ */
@SinceMirai("0.38.0")
fun <E : BotEvent> Bot.subscribeAlways( fun <E : BotEvent> Bot.subscribeAlways(
eventClass: KClass<E>, eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,
@ -376,7 +371,6 @@ inline fun <reified E : BotEvent> Bot.subscribeOnce(
* *
* @see Bot.subscribeOnce * @see Bot.subscribeOnce
*/ */
@SinceMirai("0.38.0")
fun <E : BotEvent> Bot.subscribeOnce( fun <E : BotEvent> Bot.subscribeOnce(
eventClass: KClass<E>, eventClass: KClass<E>,
coroutineContext: CoroutineContext = EmptyCoroutineContext, coroutineContext: CoroutineContext = EmptyCoroutineContext,

View File

@ -14,7 +14,6 @@ import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.data.* import net.mamoe.mirai.data.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.WeakRef import net.mamoe.mirai.utils.WeakRef
/** /**
@ -73,7 +72,6 @@ interface LowLevelBotAPIAccessor {
* 获取群公告列表 * 获取群公告列表
* @param page 页码 * @param page 页码
*/ */
@SinceMirai("0.28.0")
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelGetAnnouncements(groupId: Long, page: Int = 1, amount: Int = 10): GroupAnnouncementList suspend fun _lowLevelGetAnnouncements(groupId: Long, page: Int = 1, amount: Int = 10): GroupAnnouncementList
@ -83,7 +81,6 @@ interface LowLevelBotAPIAccessor {
* *
* @return 公告的fid * @return 公告的fid
*/ */
@SinceMirai("0.28.0")
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelSendAnnouncement(groupId: Long, announcement: GroupAnnouncement): String suspend fun _lowLevelSendAnnouncement(groupId: Long, announcement: GroupAnnouncement): String
@ -93,7 +90,6 @@ interface LowLevelBotAPIAccessor {
* 删除群公告 * 删除群公告
* @param fid [GroupAnnouncement.fid] * @param fid [GroupAnnouncement.fid]
*/ */
@SinceMirai("0.28.0")
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelDeleteAnnouncement(groupId: Long, fid: String) suspend fun _lowLevelDeleteAnnouncement(groupId: Long, fid: String)
@ -102,7 +98,6 @@ interface LowLevelBotAPIAccessor {
* 获取一条群公告 * 获取一条群公告
* @param fid [GroupAnnouncement.fid] * @param fid [GroupAnnouncement.fid]
*/ */
@SinceMirai("0.28.0")
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelGetAnnouncement(groupId: Long, fid: String): GroupAnnouncement suspend fun _lowLevelGetAnnouncement(groupId: Long, fid: String): GroupAnnouncement
@ -111,7 +106,6 @@ interface LowLevelBotAPIAccessor {
/** /**
* 获取群活跃信息 * 获取群活跃信息
*/ */
@SinceMirai("0.29.0")
@LowLevelAPI @LowLevelAPI
@MiraiExperimentalAPI @MiraiExperimentalAPI
suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData

View File

@ -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<User, Contact>(), BotEvent
/**
* 一条从服务器接收到的消息事件.
* 请查看各平台的 `actual` 实现的说明.
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "use ContactMessage",
replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage")
)
expect abstract class MessagePacket<TSender : User, TSubject : Contact> constructor() :
MessagePacketBase<TSender, TSubject>
/**
* 仅内部使用, 请使用 [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<out TSender : User, out TSubject : Contact> : 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<TSubject> =
subject.sendMessage(message.asMessageChain()) as MessageReceipt<TSubject>
suspend inline fun reply(plain: String): MessageReceipt<TSubject> =
subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt<TSubject>
// endregion
// region 图片
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
suspend inline fun ExternalImage.send(): MessageReceipt<TSubject> = this.sendTo(subject)
suspend inline fun Image.send(): MessageReceipt<TSubject> = this.sendTo(subject)
suspend inline fun Message.send(): MessageReceipt<TSubject> = this.sendTo(subject)
suspend inline fun String.send(): MessageReceipt<TSubject> = this.toMessage().sendTo(subject)
// endregion
// region 引用回复
/**
* 给这个消息事件的主体发送引用回复消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
suspend inline fun quoteReply(message: MessageChain): MessageReceipt<TSubject> =
reply(this.message.quote() + message)
suspend inline fun quoteReply(message: Message): MessageReceipt<TSubject> = reply(this.message.quote() + message)
suspend inline fun quoteReply(plain: String): MessageReceipt<TSubject> = reply(this.message.quote() + plain)
@JvmName("reply2")
suspend inline fun String.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
@JvmName("reply2")
suspend inline fun Message.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
@JvmName("reply2")
suspend inline fun MessageChain.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
// endregion
inline operator fun <M : Message> get(at: Message.Key<M>): 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<TSubject> = 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<TSubject> = 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 <reified P : ContactMessage> P.nextMessage(
timeoutMillis: Long = -1,
crossinline filter: suspend P.(P) -> Boolean
): MessageChain {
return syncFromEvent<P, P>(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 <reified P : ContactMessage> P.nextMessageOrNull(
timeoutMillis: Long = -1,
crossinline filter: suspend P.(P) -> Boolean
): MessageChain? {
return syncFromEventOrNull<P, P>(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 <reified P : ContactMessage> P.nextMessage(
timeoutMillis: Long = -1
): MessageChain {
return syncFromEvent<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessage) }
}.message
}
/**
* @see nextMessage
* @throws TimeoutCancellationException
*/
@JvmSynthetic
inline fun <reified P : ContactMessage> P.nextMessageAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<MessageChain> {
return this.bot.async(coroutineContext) {
syncFromEvent<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageAsync) }
}.message
}
}
/**
* @see nextMessage
*/
@JvmSynthetic
inline fun <reified P : ContactMessage> P.nextMessageAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline filter: suspend P.(P) -> Boolean
): Deferred<MessageChain> {
return this.bot.async(coroutineContext) {
syncFromEvent<P, P>(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 <reified P : ContactMessage> P.nextMessageOrNull(
timeoutMillis: Long = -1
): MessageChain? {
return syncFromEventOrNull<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }
}?.message
}
/**
* @see nextMessageOrNull
*/
@JvmSynthetic
inline fun <reified P : ContactMessage> P.nextMessageOrNullAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<MessageChain?> {
return this.bot.async(coroutineContext) {
syncFromEventOrNull<P, P>(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 <reified M : Message> ContactMessage.nextMessageContaining(
timeoutMillis: Long = -1
): M {
return syncFromEvent<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContaining) }
.takeIf { this.message.anyIsInstance<M>() }
}.message.firstIsInstance()
}
@JvmSynthetic
inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<M> {
return this.bot.async(coroutineContext) {
@Suppress("RemoveExplicitTypeArguments")
syncFromEvent<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) }
.takeIf { this.message.anyIsInstance<M>() }
}.message.firstIsInstance<M>()
}
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [this] 相同并含有 [M] 类型的消息的 [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @return 指定类型的消息. 超时时返回 `null`
*
* @see syncFromEventOrNull
*/
@JvmSynthetic
suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNull(
timeoutMillis: Long = -1
): M? {
return syncFromEventOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) }
.takeIf { this.message.anyIsInstance<M>() }
}?.message?.firstIsInstance()
}
@JvmSynthetic
inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<M?> {
return this.bot.async(coroutineContext) {
syncFromEventOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) }
.takeIf { this.message.anyIsInstance<M>() }
}?.message?.firstIsInstance<M>()
}
}
@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)
}

View File

@ -13,43 +13,33 @@ package net.mamoe.mirai.message
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.source import net.mamoe.mirai.message.data.source
import net.mamoe.mirai.utils.PlannedRemoval 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( class FriendMessageEvent constructor(
sender: Friend, override val sender: Friend,
override val message: MessageChain, override val message: MessageChain,
override val time: Int override val time: Int
) : ContactMessage(), BroadcastControllable { ) : @PlannedRemoval("1.2.0") FriendMessage(), 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())
init { 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" } 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 bot: Bot get() = sender.bot
override val subject: Friend get() = sender override val subject: Friend get() = sender
override val senderName: String get() = sender.nick override val senderName: String get() = sender.nick
override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend 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)"
} }

View File

@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("DEPRECATION_ERROR", "unused", "NOTHING_TO_INLINE")
package net.mamoe.mirai.message package net.mamoe.mirai.message
import net.mamoe.mirai.Bot 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.event.Event
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.PlannedRemoval 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, override val senderName: String,
/** /**
* 发送方权限. * 发送方权限.
*/ */
val permission: MemberPermission, val permission: MemberPermission,
sender: Member, override val sender: Member,
override val message: MessageChain, override val message: MessageChain,
override val time: Int override val time: Int
) : ContactMessage(), Event { ) : @PlannedRemoval("1.2.0") GroupMessage(), 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())
init { init {
val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") 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" } 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() inline val group: Group get() = sender.group
val group: Group get() = sender.group
override val bot: Bot get() = sender.bot override val bot: Bot get() = sender.bot
override val subject: Group get() = group override val subject: Group get() = group
@ -52,5 +49,5 @@ class GroupMessage(
inline fun At.asMember(): Member = group[this.target] inline fun At.asMember(): Member = group[this.target]
override fun toString(): String = 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)"
} }

View File

@ -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<User, Contact> {
/**
* 与这个消息事件相关的 [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<out TSender : User, out TSubject : Contact> :
MessageEventPlatformExtensions<TSender, TSubject> {
// region 发送 Message
/**
* 给这个消息事件的主体发送消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
@JvmSynthetic
suspend inline fun reply(message: Message): MessageReceipt<TSubject> =
subject.sendMessage(message.asMessageChain()) as MessageReceipt<TSubject>
@JvmSynthetic
suspend inline fun reply(plain: String): MessageReceipt<TSubject> =
subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt<TSubject>
// endregion
@JvmSynthetic
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
@JvmSynthetic
suspend inline fun ExternalImage.send(): MessageReceipt<TSubject> = this.sendTo(subject)
@JvmSynthetic
suspend inline fun Image.send(): MessageReceipt<TSubject> = this.sendTo(subject)
@JvmSynthetic
suspend inline fun Message.send(): MessageReceipt<TSubject> = this.sendTo(subject)
@JvmSynthetic
suspend inline fun String.send(): MessageReceipt<TSubject> = this.toMessage().sendTo(subject)
// region 引用回复
/**
* 给这个消息事件的主体发送引用回复消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
@JvmSynthetic
suspend inline fun quoteReply(message: MessageChain): MessageReceipt<TSubject> =
reply(this.message.quote() + message)
@JvmSynthetic
suspend inline fun quoteReply(message: Message): MessageReceipt<TSubject> = reply(this.message.quote() + message)
@JvmSynthetic
suspend inline fun quoteReply(plain: String): MessageReceipt<TSubject> = reply(this.message.quote() + plain)
@JvmSynthetic
inline operator fun <M : Message> get(at: Message.Key<M>): 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<out TSender : User, out TSubject : Contact> {
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<out TSender : User, out TSubject : Contact> : 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<User, Contact>(),
BotEvent, MessageEventExtensions<User, Contact>
@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<User, Contact>
@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()

View File

@ -19,7 +19,6 @@ import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recallIn import net.mamoe.mirai.recallIn
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.internal.runBlocking import net.mamoe.mirai.utils.internal.runBlocking
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@ -35,7 +34,7 @@ import kotlin.jvm.JvmSynthetic
* @param target 消息发送对象 * @param target 消息发送对象
* *
* @see Group.sendMessage 发送群消息, 返回回执此对象 * @see Group.sendMessage 发送群消息, 返回回执此对象
* @see QQ.sendMessage 发送群消息, 返回回执此对象 * @see User.sendMessage 发送群消息, 返回回执此对象
* @see Member.sendMessage 发送临时消息, 返回回执此对象 * @see Member.sendMessage 发送临时消息, 返回回执此对象
* *
* @see MessageReceipt.sourceId id * @see MessageReceipt.sourceId id
@ -48,7 +47,7 @@ open class MessageReceipt<out C : Contact>(
*/ */
val source: OnlineMessageSource.Outgoing, val source: OnlineMessageSource.Outgoing,
/** /**
* 发送目标, [Group] [QQ] [Member] * 发送目标, [Group] [Friend] [Member]
*/ */
val target: C, val target: C,
@ -89,16 +88,6 @@ open class MessageReceipt<out C : Contact>(
fun __quoteBlockingForJava__(): QuoteReply { fun __quoteBlockingForJava__(): QuoteReply {
return this.quote() 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)
}
} }
/** /**

View File

@ -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()
}

View File

@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION_ERROR", "unused", "NOTHING_TO_INLINE")
package net.mamoe.mirai.message package net.mamoe.mirai.message
import net.mamoe.mirai.Bot 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.MessageSource
import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.OnlineMessageSource
import net.mamoe.mirai.message.data.source import net.mamoe.mirai.message.data.source
import net.mamoe.mirai.utils.*
/** /**
* 临时会话消息 * 机器人收到的群临时会话消息的事件
*
* @see MessageEvent
*/ */
@SinceMirai("0.35.0") class TempMessageEvent(
class TempMessage( override val sender: Member,
sender: Member,
override val message: MessageChain, override val message: MessageChain,
override val time: Int override val time: Int
) : ContactMessage(), BroadcastControllable { ) : TempMessage(), BroadcastControllable {
@PlannedRemoval("1.0.0")
@Deprecated("", level = DeprecationLevel.HIDDEN)
constructor(sender: Member, message: MessageChain) :
this(sender, message, currentTimeSeconds.toInt())
init { init {
val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message") 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" } 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 bot: Bot get() = sender.bot
override val subject: Member get() = sender override val subject: Member get() = sender
inline val group: Group get() = sender.group 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 val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp
override fun toString(): String = 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)"
} }

View File

@ -17,7 +17,6 @@ package net.mamoe.mirai.message.data
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.utils.MiraiInternalAPI
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic import kotlin.jvm.JvmStatic
@ -73,15 +72,6 @@ private constructor(val target: Long, val display: String) :
return result 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)
}
} }
/** /**

View File

@ -12,7 +12,6 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -28,8 +27,6 @@ private const val displayA = "@全体成员"
object AtAll : object AtAll :
Message.Key<AtAll>, Message.Key<AtAll>,
MessageContent { MessageContent {
@SinceMirai("0.31.2")
const val display = displayA const val display = displayA
override val typeName: String override val typeName: String
get() = "AtAll" get() = "AtAll"

View File

@ -33,7 +33,6 @@ import net.mamoe.mirai.utils.*
* *
* @see CustomMessageMetadata 自定义消息元数据 * @see CustomMessageMetadata 自定义消息元数据
*/ */
@SinceMirai("0.38.0")
@MiraiExperimentalAPI @MiraiExperimentalAPI
sealed class CustomMessage : SingleMessage { sealed class CustomMessage : SingleMessage {
/** /**
@ -181,7 +180,6 @@ sealed class CustomMessage : SingleMessage {
* @see CustomMessage 查看更多信息 * @see CustomMessage 查看更多信息
* @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素 * @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素
*/ */
@SinceMirai("0.38.0")
@MiraiExperimentalAPI @MiraiExperimentalAPI
abstract class CustomMessageMetadata : CustomMessage(), MessageMetadata { abstract class CustomMessageMetadata : CustomMessage(), MessageMetadata {
companion object Key : Message.Key<CustomMessageMetadata> { companion object Key : Message.Key<CustomMessageMetadata> {

View File

@ -12,11 +12,13 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.ContactMessage 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.message.data.ForwardMessage.DisplayStrategy
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -72,11 +74,10 @@ import kotlin.jvm.JvmSynthetic
* *
* ### 构造 * ### 构造
* - 使用 [DSL][buildForwardMessage] * - 使用 [DSL][buildForwardMessage]
* - 通过 [ContactMessage] 集合转换: [toForwardMessage] * - 通过 [MessageEvent] 集合转换: [toForwardMessage]
* *
* @see buildForwardMessage * @see buildForwardMessage
*/ */
@SinceMirai("0.39.0")
class ForwardMessage @JvmOverloads constructor( class ForwardMessage @JvmOverloads constructor(
/** /**
* 消息列表 * 消息列表
@ -184,9 +185,8 @@ class ForwardMessage @JvmOverloads constructor(
/** /**
* 转换为 [ForwardMessage] * 转换为 [ForwardMessage]
*/ */
@SinceMirai("0.39.0")
@JvmOverloads @JvmOverloads
fun Iterable<ContactMessage>.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage { fun Iterable<MessageEvent>.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage {
val iterator = this.iterator() val iterator = this.iterator()
if (!iterator.hasNext()) return ForwardMessage(emptyList(), displayStrategy) if (!iterator.hasNext()) return ForwardMessage(emptyList(), displayStrategy)
return ForwardMessage( return ForwardMessage(
@ -205,7 +205,6 @@ fun Message.toForwardMessage(
/** /**
* 转换为 [ForwardMessage] * 转换为 [ForwardMessage]
*/ */
@SinceMirai("0.39.0")
@JvmOverloads @JvmOverloads
fun Message.toForwardMessage( fun Message.toForwardMessage(
senderId: Long, senderId: Long,
@ -220,7 +219,6 @@ fun Message.toForwardMessage(
* @see ForwardMessageBuilder 查看 DSL 帮助 * @see ForwardMessageBuilder 查看 DSL 帮助
* @see ForwardMessage 查看转发消息说明 * @see ForwardMessage 查看转发消息说明
*/ */
@SinceMirai("0.39.0")
@JvmSynthetic @JvmSynthetic
inline fun buildForwardMessage( inline fun buildForwardMessage(
context: Contact, context: Contact,
@ -234,9 +232,8 @@ inline fun buildForwardMessage(
* @see ForwardMessageBuilder 查看 DSL 帮助 * @see ForwardMessageBuilder 查看 DSL 帮助
* @see ForwardMessage 查看转发消息说明 * @see ForwardMessage 查看转发消息说明
*/ */
@SinceMirai("0.39.0")
@JvmSynthetic @JvmSynthetic
inline fun ContactMessage.buildForwardMessage( inline fun MessageEvent.buildForwardMessage(
context: Contact = this.subject, context: Contact = this.subject,
displayStrategy: DisplayStrategy = DisplayStrategy, displayStrategy: DisplayStrategy = DisplayStrategy,
block: ForwardMessageBuilder.() -> Unit block: ForwardMessageBuilder.() -> Unit
@ -247,7 +244,6 @@ inline fun ContactMessage.buildForwardMessage(
/** /**
* 标记转发消息 DSL * 标记转发消息 DSL
*/ */
@SinceMirai("0.39.0")
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@DslMarker @DslMarker
annotation class ForwardMessageDsl annotation class ForwardMessageDsl
@ -311,7 +307,6 @@ annotation class ForwardMessageDsl
* *
* `S named "name1" named "name2" says M` 最终的发送人名称为 `"name2"` * `S named "name1" named "name2" says M` 最终的发送人名称为 `"name2"`
*/ */
@SinceMirai("0.39.0")
class ForwardMessageBuilder private constructor( class ForwardMessageBuilder private constructor(
/** /**
* 消息语境. 可为 [Group] [User] * 消息语境. 可为 [Group] [User]

View File

@ -17,7 +17,6 @@ import net.mamoe.mirai.message.data.PokeMessage.Types
import net.mamoe.mirai.message.data.VipFace.Companion import net.mamoe.mirai.message.data.VipFace.Companion
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.* import kotlin.jvm.*
/** /**
@ -26,7 +25,6 @@ import kotlin.jvm.*
* @see PokeMessage 戳一戳 * @see PokeMessage 戳一戳
* @see FlashImage 闪照 * @see FlashImage 闪照
*/ */
@SinceMirai("0.31.0")
sealed class HummerMessage : MessageContent { sealed class HummerMessage : MessageContent {
companion object Key : Message.Key<HummerMessage> { companion object Key : Message.Key<HummerMessage> {
override val typeName: String override val typeName: String
@ -44,7 +42,6 @@ sealed class HummerMessage : MessageContent {
* *
* @see Types 使用伴生对象中的常量 * @see Types 使用伴生对象中的常量
*/ */
@SinceMirai("0.31.0")
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
data class PokeMessage internal constructor( data class PokeMessage internal constructor(
/** /**
@ -158,7 +155,6 @@ data class PokeMessage internal constructor(
* *
* @see Types 使用伴生对象中的常量 * @see Types 使用伴生对象中的常量
*/ */
@SinceMirai("0.39.5")
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
data class VipFace internal constructor( data class VipFace internal constructor(
/** /**
@ -241,7 +237,6 @@ data class VipFace internal constructor(
* *
* @see Image 查看图片相关信息 * @see Image 查看图片相关信息
*/ */
@SinceMirai("0.33.0")
sealed class FlashImage : MessageContent, HummerMessage() { sealed class FlashImage : MessageContent, HummerMessage() {
companion object Key : Message.Key<FlashImage> { companion object Key : Message.Key<FlashImage> {
/** /**
@ -289,22 +284,17 @@ sealed class FlashImage : MessageContent, HummerMessage() {
override fun toString(): String = stringValue!! override fun toString(): String = stringValue!!
override fun contentToString(): String = "[闪照]" override fun contentToString(): String = "[闪照]"
} }
@SinceMirai("0.33.0")
inline fun Image.flash(): FlashImage = FlashImage(this) inline fun Image.flash(): FlashImage = FlashImage(this)
@JvmSynthetic @JvmSynthetic
@SinceMirai("0.33.0")
inline fun GroupImage.flash(): GroupFlashImage = FlashImage(this) as GroupFlashImage inline fun GroupImage.flash(): GroupFlashImage = FlashImage(this) as GroupFlashImage
@JvmSynthetic @JvmSynthetic
@SinceMirai("0.33.0")
inline fun FriendImage.flash(): FriendFlashImage = FlashImage(this) as FriendFlashImage inline fun FriendImage.flash(): FriendFlashImage = FlashImage(this) as FriendFlashImage
/** /**
* @see FlashImage.invoke * @see FlashImage.invoke
*/ */
@SinceMirai("0.33.0")
data class GroupFlashImage(override val image: GroupImage) : FlashImage() { data class GroupFlashImage(override val image: GroupImage) : FlashImage() {
companion object Key : Message.Key<GroupFlashImage> { companion object Key : Message.Key<GroupFlashImage> {
override val typeName: String override val typeName: String
@ -315,7 +305,6 @@ data class GroupFlashImage(override val image: GroupImage) : FlashImage() {
/** /**
* @see FlashImage.invoke * @see FlashImage.invoke
*/ */
@SinceMirai("0.33.0")
data class FriendFlashImage(override val image: FriendImage) : FlashImage() { data class FriendFlashImage(override val image: FriendImage) : FlashImage() {
companion object Key : Message.Key<FriendFlashImage> { companion object Key : Message.Key<FriendFlashImage> {
override val typeName: String override val typeName: String

View File

@ -22,7 +22,6 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.SinceMirai
import kotlin.js.JsName import kotlin.js.JsName
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -45,6 +44,10 @@ import kotlin.jvm.JvmSynthetic
* @see Contact.sendImage 上传 [图片文件][ExternalImage] 并发送返回的 [Image] 作为一条消息 * @see Contact.sendImage 上传 [图片文件][ExternalImage] 并发送返回的 [Image] 作为一条消息
* @see Image.sendTo 上传图片并得到 [Image] 消息 * @see Image.sendTo 上传图片并得到 [Image] 消息
* *
* ### 下载图片
* @see Image.queryUrl 扩展函数. 查询图片下载链接
* @see Bot.queryImageUrl 查询图片下载链接 (Java 使用)
*
* 查看平台 `actual` 定义以获取上传方式扩展. * 查看平台 `actual` 定义以获取上传方式扩展.
* *
* @see FlashImage 闪照 * @see FlashImage 闪照
@ -94,6 +97,7 @@ expect interface Image : Message, MessageContent {
* @property imageId 形如 `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (后缀一定为 `".mirai"`) * @property imageId 形如 `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (后缀一定为 `".mirai"`)
* @see Image 查看更多说明 * @see Image 查看更多说明
*/ */
@PlannedRemoval("1.2.0") // make internal
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
// CustomFace // CustomFace
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@ -109,7 +113,6 @@ sealed class GroupImage : AbstractImage() {
* Java 使用: `MessageUtils.calculateImageMd5(image)` * Java 使用: `MessageUtils.calculateImageMd5(image)`
*/ */
@get:JvmName("calculateImageMd5") @get:JvmName("calculateImageMd5")
@SinceMirai("0.39.0")
val Image.md5: ByteArray val Image.md5: ByteArray
get() = calculateImageMd5ByImageId(imageId) get() = calculateImageMd5ByImageId(imageId)
@ -119,6 +122,7 @@ val Image.md5: ByteArray
* *
* [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度) `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度) * [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度) `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度)
*/ // NotOnlineImage */ // NotOnlineImage
@PlannedRemoval("1.2.0") // make internal
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
@OptIn(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
sealed class FriendImage : AbstractImage() { sealed class FriendImage : AbstractImage() {
@ -133,7 +137,6 @@ sealed class FriendImage : AbstractImage() {
* `/f8f1ab55-bf8e-4236-b55e-955848d7069f` * `/f8f1ab55-bf8e-4236-b55e-955848d7069f`
* @see FRIEND_IMAGE_ID_REGEX_2 * @see FRIEND_IMAGE_ID_REGEX_2
*/ */
@SinceMirai("0.39.2")
// Java: MessageUtils.FRIEND_IMAGE_ID_REGEX_1 // 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}""") 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` * `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206`
* @see FRIEND_IMAGE_ID_REGEX_1 * @see FRIEND_IMAGE_ID_REGEX_1
*/ */
@SinceMirai("0.39.2")
// Java: MessageUtils.FRIEND_IMAGE_ID_REGEX_2 // Java: MessageUtils.FRIEND_IMAGE_ID_REGEX_2
val FRIEND_IMAGE_ID_REGEX_2 = Regex("""/[0-9]*-[0-9]*-[0-9a-fA-F]{32}""") 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` * `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai`
*/ */
@Suppress("RegExpRedundantEscape") // This is required on Android @Suppress("RegExpRedundantEscape") // This is required on Android
@SinceMirai("0.39.2")
// Java: MessageUtils.GROUP_IMAGE_ID_REGEX // 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""") 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] 以便发送. * 通过 [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_1 -> OfflineFriendImage(imageId)
imageId matches FRIEND_IMAGE_ID_REGEX_2 -> 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 -> OfflineGroupImage(imageId)
imageId matches GROUP_IMAGE_ID_REGEX_OLD -> OfflineGroupImage(imageId)
else -> throw IllegalArgumentException("Illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE") else -> throw IllegalArgumentException("Illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE")
} }
@ -279,7 +268,7 @@ data class OfflineGroupImage(
) : GroupImage(), OfflineImage { ) : GroupImage(), OfflineImage {
init { init {
@Suppress("DEPRECATION") @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" "Illegal imageId. It must matches GROUP_IMAGE_ID_REGEX"
} }
} }
@ -329,18 +318,10 @@ abstract class OnlineFriendImage : FriendImage(), OnlineImage
// endregion // 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] 实现的基类. * 所有 [Image] 实现的基类.
*/ */
@PlannedRemoval("1.2.0") // make internal
@Deprecated( @Deprecated(
"This is internal API. Use Image instead", "This is internal API. Use Image instead",
level = DeprecationLevel.HIDDEN, // so that others can't see this class level = DeprecationLevel.HIDDEN, // so that others can't see this class

View File

@ -14,6 +14,7 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message.Key import net.mamoe.mirai.message.data.Message.Key
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
@ -26,8 +27,6 @@ import kotlin.jvm.JvmSynthetic
/** /**
* 可发送的或从服务器接收的消息. * 可发送的或从服务器接收的消息.
* *
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] .
*
* [消息][Message] 分为 * [消息][Message] 分为
* - [SingleMessage]: * - [SingleMessage]:
* - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource], [引用回复][QuoteReply]. * - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource], [引用回复][QuoteReply].
@ -63,13 +62,18 @@ import kotlin.jvm.JvmSynthetic
* *
* , `appendable.append(message)` 相当于 `appendable.append(message.toString())` * , `appendable.append(message)` 相当于 `appendable.append(message.toString())`
* *
* #### 发送消息
* - 通过 [Contact] 中的成员函数: [Contact.sendMessage]
* - 通过 [Message] 的扩展函数: [Message.sendTo]
* - [MessageEvent] 中使用 [MessageEvent.reply] 等捷径
*
* @see PlainText 纯文本 * @see PlainText 纯文本
* @see Image 图片 * @see Image 图片
* @see Face 原生表情 * @see Face 原生表情
* @see At 一个群成员的引用 * @see At 一个群成员的引用
* @see AtAll 全体成员的引用 * @see AtAll 全体成员的引用
* @see QuoteReply 一条消息的引用 * @see QuoteReply 一条消息的引用
* @see RichMessage 富文本消息, [Xml][XmlMessage], [小程序][LightApp], [Json][JsonMessage] * @see RichMessage 富文本消息, [XML JSON][ServiceMessage], [小程序][LightApp]
* @see HummerMessage 一些特殊的消息, [闪照][FlashImage], [戳一戳][PokeMessage] * @see HummerMessage 一些特殊的消息, [闪照][FlashImage], [戳一戳][PokeMessage]
* @see CustomMessage 自定义消息类型 * @see CustomMessage 自定义消息类型
* *
@ -95,7 +99,6 @@ interface Message { // must be interface. Don't consider any changes.
/** /**
* [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName`, "QuoteReply", "PlainText" * [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName`, "QuoteReply", "PlainText"
*/ */
@SinceMirai("0.34.0")
val typeName: String val typeName: String
} }
@ -118,7 +121,6 @@ interface Message { // must be interface. Don't consider any changes.
* *
* @see plus `+` 操作符重载 * @see plus `+` 操作符重载
*/ */
@SinceMirai("0.34.0")
@JvmSynthetic // in java they should use `plus` instead @JvmSynthetic // in java they should use `plus` instead
fun followedBy(tail: Message): MessageChain = followedByImpl(tail) fun followedBy(tail: Message): MessageChain = followedByImpl(tail)
@ -151,7 +153,6 @@ interface Message { // must be interface. Don't consider any changes.
* *
* @see toString 得到包含 mirai 消息元素代码的, 易读的字符串 * @see toString 得到包含 mirai 消息元素代码的, 易读的字符串
*/ */
@SinceMirai("0.34.0")
fun contentToString(): String fun contentToString(): String
@ -164,7 +165,6 @@ interface Message { // must be interface. Don't consider any changes.
* *
* @sample net.mamoe.mirai.message.data.ContentEqualsTest * @sample net.mamoe.mirai.message.data.ContentEqualsTest
*/ */
@SinceMirai("0.38.0")
fun contentEquals(another: Message, ignoreCase: Boolean = false): Boolean = contentEqualsImpl(another, ignoreCase) 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 * @sample net.mamoe.mirai.message.data.ContentEqualsTest
*/ */
@SinceMirai("0.38.0")
fun contentEquals(another: String, ignoreCase: Boolean = false): Boolean { fun contentEquals(another: String, ignoreCase: Boolean = false): Boolean {
if (!this.contentToString().equals(another, ignoreCase = ignoreCase)) return false if (!this.contentToString().equals(another, ignoreCase = ignoreCase)) return false
return when (this) { 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)` // `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)`
operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage()) 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 * - [PlainText] 长度为 0
* - [MessageChain] 所有元素都满足 [isContentEmpty] * - [MessageChain] 所有元素都满足 [isContentEmpty]
*/ */
@SinceMirai("0.39.3")
fun Message.isContentEmpty(): Boolean = when (this) { fun Message.isContentEmpty(): Boolean = when (this) {
is MessageMetadata -> true is MessageMetadata -> true
is PlainText -> this.content.isEmpty() is PlainText -> this.content.isEmpty()
is MessageChain -> this.all { it.isContentEmpty() } is MessageChain -> this.all { it.isContentEmpty() }
else -> false else -> false
} }
@SinceMirai("0.39.3")
inline fun Message.isContentNotEmpty(): Boolean = !this.isContentEmpty() inline fun Message.isContentNotEmpty(): Boolean = !this.isContentEmpty()
inline fun Message.isPlain(): Boolean = this is PlainText 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") @Suppress("OverridingDeprecatedMember")
interface SingleMessage : Message { interface SingleMessage : Message {
@PlannedRemoval("1.0.0") @PlannedRemoval("1.2.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")
@JvmSynthetic @JvmSynthetic
@SinceMirai("1.0.0") @SinceMirai("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun length(): Int = this.toString().length fun length(): Int = this.toString().length
@PlannedRemoval("1.1.0") @PlannedRemoval("1.2.0")
@JvmSynthetic @JvmSynthetic
@SinceMirai("1.0.0") @SinceMirai("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun charAt(index: Int): Char = this.toString()[index] fun charAt(index: Int): Char = this.toString()[index]
@PlannedRemoval("1.1.0") @PlannedRemoval("1.2.0")
@JvmSynthetic @JvmSynthetic
@SinceMirai("1.0.0") @SinceMirai("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN) @Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@ -379,7 +283,6 @@ interface MessageMetadata : SingleMessage
* *
* 实现此接口的元素将会在连接时自动处理替换. * 实现此接口的元素将会在连接时自动处理替换.
*/ */
@SinceMirai("0.34.0")
interface ConstrainSingle<out M : Message> : MessageMetadata { interface ConstrainSingle<out M : Message> : MessageMetadata {
val key: Key<M> val key: Key<M>
} }

View File

@ -10,13 +10,13 @@
@file:JvmMultifileClass @file:JvmMultifileClass
@file:JvmName("MessageUtils") @file:JvmName("MessageUtils")
@file:Suppress("unused", "NOTHING_TO_INLINE") @file:Suppress("unused", "NOTHING_TO_INLINE")
@file:OptIn(MiraiInternalAPI::class)
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.SinceMirai
import kotlin.js.JsName import kotlin.js.JsName
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -32,7 +32,7 @@ import kotlin.reflect.KProperty
* @see asMessageChain 将单个 [Message] 转换为 [MessageChain] * @see asMessageChain 将单个 [Message] 转换为 [MessageChain]
* @see asMessageChain [Iterable] [Sequence] 委托为 [MessageChain] * @see asMessageChain [Iterable] [Sequence] 委托为 [MessageChain]
* *
* @see foreachContent 遍历内容 * @see forEachContent 遍历内容
* *
* @see orNull 属性委托扩展 * @see orNull 属性委托扩展
* @see orElse 属性委托扩展 * @see orElse 属性委托扩展
@ -40,18 +40,9 @@ import kotlin.reflect.KProperty
* @see flatten 扁平化 * @see flatten 扁平化
*/ */
interface MessageChain : Message, Iterable<SingleMessage> { interface MessageChain : Message, Iterable<SingleMessage> {
@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] 不参加计数. * 元素数量. [EmptyMessageChain] 不参加计数.
*/ */
@SinceMirai("0.31.1")
val size: Int val size: Int
/** /**
@ -60,56 +51,42 @@ interface MessageChain : Message, Iterable<SingleMessage> {
* @param key 由各个类型消息的伴生对象持有. [PlainText.Key] * @param key 由各个类型消息的伴生对象持有. [PlainText.Key]
* @throws NoSuchElementException 当找不到这个类型的 [Message] * @throws NoSuchElementException 当找不到这个类型的 [Message]
*/ */
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "INAPPLICABLE_JVM_NAME")
@JvmName("first") @JvmName("first")
/* final */ operator fun <M : Message> get(key: Message.Key<M>): M = first(key) final operator fun <M : Message> get(key: Message.Key<M>): M = first(key)
/** /**
* 获取第一个类型为 [key] [Message] 实例, 找不到则返回 `null` * 获取第一个类型为 [key] [Message] 实例, 找不到则返回 `null`
* *
* @param key 由各个类型消息的伴生对象持有. [PlainText.Key] * @param key 由各个类型消息的伴生对象持有. [PlainText.Key]
*/ */
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "INAPPLICABLE_JVM_NAME")
@JvmName("firstOrNull") @JvmName("firstOrNull")
/* final */ fun <M : Message> getOrNull(key: Message.Key<M>): M? = firstOrNull(key) final fun <M : Message> getOrNull(key: Message.Key<M>): M? = firstOrNull(key)
/** /**
* 遍历每一个有内容的消息, [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]. * 遍历每一个有内容的消息, [At], [AtAll], [PlainText], [Image], [Face]
* 仅供 `Java` 使用 * 仅供 `Java` 使用
*/ */
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "FunctionName", "INAPPLICABLE_JVM_NAME")
@JsName("forEachContent") @JsName("forEachContent")
@JvmName("forEachContent") @JvmName("forEachContent")
@MiraiInternalAPI @JavaFriendlyAPI
fun `__forEachContent for Java__`(block: (Message) -> Unit) { final fun __forEachContentForJava__(block: (Message) -> Unit) {
this.forEachContent(block) this.forEachContent(block)
} }
/** /**
* 遍历每一个消息, [MessageSource] [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]. * 遍历每一个消息, [MessageSource] [At], [AtAll], [PlainText], [Image], [QuoteReply]
* 仅供 `Java` 使用 * 仅供 `Java` 使用
*/ */
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") @Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "FunctionName", "INAPPLICABLE_JVM_NAME")
@JsName("forEach") @JsName("forEach")
@JvmName("forEach") @JvmName("forEach")
@MiraiInternalAPI @JavaFriendlyAPI
fun `__forEach for Java__`(block: (Message) -> Unit) { final fun __forEachForJava__(block: (Message) -> Unit) {
this.forEach(block) this.forEach(block)
} }
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmSynthetic
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
@JvmName("get")
fun <M : Message> get2(key: Message.Key<M>): M = first(key)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmSynthetic
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
@JvmName("getOrNull")
fun <M : Message> getOrNull2(key: Message.Key<M>): M? = getOrNull(key)
} }
// region accessors // region accessors
@ -117,7 +94,6 @@ interface MessageChain : Message, Iterable<SingleMessage> {
/** /**
* 遍历每一个 [消息内容][MessageContent] * 遍历每一个 [消息内容][MessageContent]
*/ */
@SinceMirai("0.39.0")
@JvmSynthetic @JvmSynthetic
inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) { inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) {
for (element in this) { 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` * 如果每一个 [消息内容][MessageContent] 都满足 [block], 返回 `true`
*/ */
@ -436,17 +406,3 @@ object EmptyMessageChain : MessageChain, Iterator<SingleMessage> {
override fun hasNext(): Boolean = false override fun hasNext(): Boolean = false
override fun next(): SingleMessage = throw NoSuchElementException("EmptyMessageChain is empty.") 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<SingleMessage> = error("accessing NullMessageChain")
}

View File

@ -14,7 +14,6 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic import kotlin.jvm.JvmSynthetic
@ -159,7 +158,6 @@ open class MessageChainBuilder private constructor(
/** /**
* 将所有已有元素引用复制到一个新的 [MessageChainBuilder] * 将所有已有元素引用复制到一个新的 [MessageChainBuilder]
*/ */
@SinceMirai("0.38.0")
fun copy(): MessageChainBuilder { fun copy(): MessageChainBuilder {
return MessageChainBuilder(container.toMutableList()) return MessageChainBuilder(container.toMutableList())
} }

View File

@ -16,10 +16,12 @@ package net.mamoe.mirai.message.data
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.* 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.message.MessageReceipt
import net.mamoe.mirai.recallIn 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.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
@ -57,7 +59,6 @@ import kotlin.jvm.JvmSynthetic
* @see OfflineMessageSource 离线消息的 [MessageSource] * @see OfflineMessageSource 离线消息的 [MessageSource]
*/ */
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
@SinceMirai("0.33.0")
sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSource> { sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSource> {
companion object Key : Message.Key<MessageSource> { companion object Key : Message.Key<MessageSource> {
override val typeName: String get() = "MessageSource" override val typeName: String get() = "MessageSource"
@ -93,7 +94,6 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
* *
* 在事件中和在引用中无法保证同一条消息的 [internalId] 相同. * 在事件中和在引用中无法保证同一条消息的 [internalId] 相同.
*/ */
@SinceMirai("0.39.0")
abstract val internalId: Int abstract val internalId: Int
/** /**
@ -156,7 +156,7 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
* 此回执的 [消息源][MessageReceipt.source] 即为一个 [外向消息源][OnlineMessageSource.Outgoing], 代表着刚刚发出的那条消息的来源. * 此回执的 [消息源][MessageReceipt.source] 即为一个 [外向消息源][OnlineMessageSource.Outgoing], 代表着刚刚发出的那条消息的来源.
* *
* #### 机器人接受消息 * #### 机器人接受消息
* 当机器人接收一条消息 [ContactMessage], 这条消息包含一个 [内向消息源][OnlineMessageSource.Incoming], 代表着接收到的这条消息的来源. * 当机器人接收一条消息 [MessageEvent], 这条消息包含一个 [内向消息源][OnlineMessageSource.Incoming], 代表着接收到的这条消息的来源.
* *
* *
* ### 实现 * ### 实现
@ -164,7 +164,6 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
* *
* @see OnlineMessageSource.toOffline 转为 [OfflineMessageSource] * @see OnlineMessageSource.toOffline 转为 [OfflineMessageSource]
*/ */
@SinceMirai("0.33.0")
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
sealed class OnlineMessageSource : MessageSource() { sealed class OnlineMessageSource : MessageSource() {
companion object Key : Message.Key<OnlineMessageSource> { companion object Key : Message.Key<OnlineMessageSource> {
@ -279,34 +278,7 @@ sealed class OnlineMessageSource : MessageSource() {
final override val target: Group get() = group final override val target: Group get() = group
inline val group: Group get() = sender.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] * @see buildMessageSource 构建一个 [OfflineMessageSource]
*/ */
@SinceMirai("0.33.0")
abstract class OfflineMessageSource : MessageSource() { abstract class OfflineMessageSource : MessageSource() {
companion object Key : Message.Key<OfflineMessageSource> { companion object Key : Message.Key<OfflineMessageSource> {
override val typeName: String override val typeName: String
@ -325,8 +296,6 @@ abstract class OfflineMessageSource : MessageSource() {
enum class Kind { enum class Kind {
GROUP, GROUP,
FRIEND, FRIEND,
@SinceMirai("0.36.0")
TEMP TEMP
} }
@ -380,7 +349,7 @@ fun MessageSource.quote(): QuoteReply {
} }
/** /**
* 引用这条消息. 仅从服务器接收的消息 (即来自 [ContactMessage]) 才可以通过这个方式被引用. * 引用这条消息. 仅从服务器接收的消息 (即来自 [MessageEvent]) 才可以通过这个方式被引用.
* @see QuoteReply * @see QuoteReply
*/ */
fun MessageChain.quote(): QuoteReply { fun MessageChain.quote(): QuoteReply {

View File

@ -19,7 +19,6 @@ import net.mamoe.mirai.contact.Friend
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -28,7 +27,6 @@ import kotlin.jvm.JvmSynthetic
/** /**
* 将在线消息源转换为离线消息源. * 将在线消息源转换为离线消息源.
*/ */
@SinceMirai("0.39.0")
@JvmName("toOfflineMessageSource") @JvmName("toOfflineMessageSource")
fun OnlineMessageSource.toOffline(): OfflineMessageSource = fun OnlineMessageSource.toOffline(): OfflineMessageSource =
OfflineMessageSourceByOnline(this) OfflineMessageSourceByOnline(this)
@ -44,7 +42,6 @@ fun OnlineMessageSource.toOffline(): OfflineMessageSource =
* @see buildMessageSource 查看更多说明 * @see buildMessageSource 查看更多说明
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.39.0")
@JvmName("copySource") @JvmName("copySource")
fun MessageSource.copyAmend( fun MessageSource.copyAmend(
block: MessageSourceAmender.() -> Unit block: MessageSourceAmender.() -> Unit
@ -53,7 +50,6 @@ fun MessageSource.copyAmend(
/** /**
* 仅于 [copyAmend] 中修改 [MessageSource] * 仅于 [copyAmend] 中修改 [MessageSource]
*/ */
@SinceMirai("0.39.0")
interface MessageSourceAmender { interface MessageSourceAmender {
var kind: OfflineMessageSource.Kind var kind: OfflineMessageSource.Kind
var fromUin: Long var fromUin: Long
@ -65,7 +61,6 @@ interface MessageSourceAmender {
var originalMessage: MessageChain var originalMessage: MessageChain
/** 从另一个 [MessageSource] 中复制 [id], [internalId], [time]*/ /** 从另一个 [MessageSource] 中复制 [id], [internalId], [time]*/
@SinceMirai("0.39.2")
fun metadataFrom(another: MessageSource) { fun metadataFrom(another: MessageSource) {
this.id = another.id this.id = another.id
this.internalId = another.internalId this.internalId = another.internalId
@ -107,7 +102,6 @@ interface MessageSourceAmender {
* } * }
* ``` * ```
*/ */
@SinceMirai("0.39.0")
@JvmSynthetic @JvmSynthetic
@MiraiExperimentalAPI @MiraiExperimentalAPI
fun Bot.buildMessageSource(block: MessageSourceBuilder.() -> Unit): MessageSource { fun Bot.buildMessageSource(block: MessageSourceBuilder.() -> Unit): MessageSource {

View File

@ -29,7 +29,7 @@ data class PlainText(
val content: String val content: String
) : MessageContent { ) : MessageContent {
@PlannedRemoval("1.1.0") @PlannedRemoval("1.2.0")
@Deprecated( @Deprecated(
"use content instead for clearer semantics", "use content instead for clearer semantics",
level = DeprecationLevel.WARNING, level = DeprecationLevel.WARNING,

View File

@ -16,7 +16,6 @@ package net.mamoe.mirai.message.data
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.SinceMirai
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
@ -43,7 +42,6 @@ import kotlin.jvm.JvmSynthetic
* @see MessageSource 获取有关消息源的更多信息 * @see MessageSource 获取有关消息源的更多信息
*/ */
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
@SinceMirai("0.33.0")
class QuoteReply(val source: MessageSource) : Message, MessageMetadata, ConstrainSingle<QuoteReply> { class QuoteReply(val source: MessageSource) : Message, MessageMetadata, ConstrainSingle<QuoteReply> {
companion object Key : Message.Key<QuoteReply> { companion object Key : Message.Key<QuoteReply> {
override val typeName: String override val typeName: String
@ -68,7 +66,6 @@ inline val QuoteReply.id: Int
/** /**
* @see MessageSource.internalId * @see MessageSource.internalId
*/ */
@SinceMirai("0.39.2")
@get:JvmSynthetic @get:JvmSynthetic
inline val QuoteReply.internalId: Int inline val QuoteReply.internalId: Int
get() = source.internalId get() = source.internalId

View File

@ -14,8 +14,6 @@
package net.mamoe.mirai.message.data package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.SinceMirai
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
@ -31,7 +29,6 @@ import kotlin.jvm.JvmSynthetic
* @see LightApp 小程序 (JSON) * @see LightApp 小程序 (JSON)
*/ */
// not using sealed class for customized implementations // not using sealed class for customized implementations
@SinceMirai("0.27.0")
interface RichMessage : MessageContent { interface RichMessage : MessageContent {
/** /**
@ -50,14 +47,12 @@ interface RichMessage : MessageContent {
* @suppress API 不稳定, 可能在任意时刻被删除 * @suppress API 不稳定, 可能在任意时刻被删除
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.30.0")
companion object Templates : Message.Key<RichMessage> { companion object Templates : Message.Key<RichMessage> {
/** /**
* @suppress API 不稳定, 可能在任意时刻被删除 * @suppress API 不稳定, 可能在任意时刻被删除
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
@SinceMirai("0.30.0")
fun share( fun share(
url: String, url: String,
title: String? = null, 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 override val typeName: String
get() = "RichMessage" get() = "RichMessage"
} }
@ -112,7 +93,6 @@ interface RichMessage : MessageContent {
* *
* @see ServiceMessage 服务消息 * @see ServiceMessage 服务消息
*/ */
@SinceMirai("0.27.0")
data class LightApp(override val content: String) : RichMessage { data class LightApp(override val content: String) : RichMessage {
companion object Key : Message.Key<LightApp> { companion object Key : Message.Key<LightApp> {
override val typeName: String get() = "LightApp" override val typeName: String get() = "LightApp"
@ -131,7 +111,6 @@ data class LightApp(override val content: String) : RichMessage {
* *
* @see LightApp 小程序类型消息 * @see LightApp 小程序类型消息
*/ */
@SinceMirai("0.37.3")
open class ServiceMessage(val serviceId: Int, final override val content: String) : RichMessage { open class ServiceMessage(val serviceId: Int, final override val content: String) : RichMessage {
companion object Key : Message.Key<ServiceMessage> { companion object Key : Message.Key<ServiceMessage> {
override val typeName: String get() = "ServiceMessage" 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<JsonMessage> {
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<XmlMessage> {
override val typeName: String get() = "XmlMessage"
}
}
/* /*
commonElem=CommonElem#750141174 { commonElem=CommonElem#750141174 {
businessType=0x00000001(1) businessType=0x00000001(1)
@ -221,10 +147,9 @@ commonElem=CommonElem#750141174 {
*/ */
@Suppress("DEPRECATION_ERROR") @Suppress("DEPRECATION_ERROR")
@JvmSynthetic @JvmSynthetic
@SinceMirai("0.27.0")
@MiraiExperimentalAPI @MiraiExperimentalAPI
inline fun buildXmlMessage(serviceId: Int, block: @XmlMessageDsl XmlMessageBuilder.() -> Unit): ServiceMessage = inline fun buildXmlMessage(serviceId: Int, block: @XmlMessageDsl XmlMessageBuilder.() -> Unit): ServiceMessage =
XmlMessage(serviceId, XmlMessageBuilder().apply(block).text) ServiceMessage(serviceId, XmlMessageBuilder().apply(block).text)
@MiraiExperimentalAPI @MiraiExperimentalAPI
@Target(CLASS, FUNCTION, TYPE) @Target(CLASS, FUNCTION, TYPE)
@ -273,8 +198,6 @@ class XmlMessageBuilder(
sourceName = name sourceName = name
sourceIconURL = iconURL sourceIconURL = iconURL
} }
@SinceMirai("0.27.0")
@XmlMessageDsl @XmlMessageDsl
class ItemBuilder @PublishedApi internal constructor( class ItemBuilder @PublishedApi internal constructor(
var bg: Int = 0, 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 @MiraiExperimentalAPI
internal class LongMessage internal constructor(content: String, val resId: String) : ServiceMessage(35, content) { internal class LongMessage internal constructor(content: String, val resId: String) : ServiceMessage(35, content) {
companion object Key : Message.Key<LongMessage> { companion object Key : Message.Key<LongMessage> {
@ -316,5 +228,4 @@ internal class LongMessage internal constructor(content: String, val resId: Stri
} }
@OptIn(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
@SinceMirai("0.39.0")
internal class ForwardMessageInternal(content: String) : ServiceMessage(35, content) internal class ForwardMessageInternal(content: String) : ServiceMessage(35, content)

View File

@ -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

View File

@ -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)
}
}

Some files were not shown because too many files have changed in this diff Show More