mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-02 04:30:25 +08:00
commit
8ca4357eb8
@ -101,7 +101,7 @@
|
||||
|
||||
#### `OfflineMessageSource` 构造
|
||||
可使用 DSL 构造离线消息, 修改其发送人, 发送时间, 发送内容等. 这对于跨群转发等情况十分有用.
|
||||
[OfflineMessageSource.kt: Line 90](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/OfflineMessageSource.kt#L90)
|
||||
[MessageSourceBuilder.kt: Line 90](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSourceBuilder.kt#L90)
|
||||
DSL 总览:
|
||||
```
|
||||
val source: OfflineMessageSource = bot.buildMessageSource {
|
||||
|
@ -6,7 +6,7 @@ import kotlin.math.pow
|
||||
buildscript {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
|
||||
// maven(url = "https://mirrors.huaweicloud.com/repository/maven")
|
||||
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
jcenter()
|
||||
google()
|
||||
@ -51,7 +51,7 @@ allprojects {
|
||||
version = Versions.Mirai.version
|
||||
|
||||
repositories {
|
||||
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
|
||||
// maven(url = "https://mirrors.huaweicloud.com/repository/maven")
|
||||
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
jcenter()
|
||||
google()
|
||||
|
@ -24,6 +24,8 @@ object Versions {
|
||||
const val dokka = "0.10.1"
|
||||
}
|
||||
|
||||
const val jcekt = "1.0.0"
|
||||
|
||||
object Android {
|
||||
const val androidGradlePlugin = "3.5.3"
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ kotlin {
|
||||
api(kotlin("stdlib", Versions.Kotlin.stdlib))
|
||||
api(kotlinx("serialization-runtime-common", Versions.Kotlin.serialization))
|
||||
api(kotlinx("serialization-protobuf-common", Versions.Kotlin.serialization))
|
||||
api("moe.him188:jcekt-common:${Versions.jcekt}")
|
||||
api("org.jetbrains.kotlinx:atomicfu:${Versions.Kotlin.atomicFU}")
|
||||
api(kotlinx("io", Versions.Kotlin.io))
|
||||
api(kotlinx("coroutines-io", Versions.Kotlin.coroutinesIo))
|
||||
@ -86,6 +87,7 @@ kotlin {
|
||||
dependencies {
|
||||
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
|
||||
// api(kotlinx("coroutines-debug", "1.3.5"))
|
||||
api("moe.him188:jcekt:${Versions.jcekt}")
|
||||
api(kotlinx("serialization-runtime", Versions.Kotlin.serialization))
|
||||
//api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization))
|
||||
|
||||
|
@ -15,7 +15,6 @@ import io.ktor.client.HttpClient
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.MultiPartFormDataContent
|
||||
import io.ktor.client.request.forms.formData
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.io.ByteReadChannel
|
||||
@ -51,7 +50,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
|
||||
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
|
||||
import net.mamoe.mirai.qqandroid.utils.encodeToString
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.qqandroid.utils.toReadPacket
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.collections.asSequence
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
@ -190,7 +188,7 @@ internal class QQAndroidBot constructor(
|
||||
}
|
||||
}
|
||||
|
||||
group.checkBotPermissionOperator()
|
||||
group.checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
}
|
||||
|
||||
override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) {
|
||||
@ -351,10 +349,6 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
return sequence
|
||||
}
|
||||
|
||||
override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
@Suppress("RemoveExplicitTypeArguments") // false positive
|
||||
override suspend fun recall(source: MessageSource) {
|
||||
check(source is MessageSourceInternal)
|
||||
@ -375,7 +369,7 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
else -> error("stub")
|
||||
}
|
||||
if (this.id != source.fromId) {
|
||||
group.checkBotPermissionOperator()
|
||||
group.checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
}
|
||||
MessageRecallEvent.GroupRecall(
|
||||
this,
|
||||
@ -679,8 +673,8 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
response.proto.uint32UpIp.zip(response.proto.uint32UpPort),
|
||||
response.proto.msgSig,
|
||||
MiraiPlatformUtils.md5(body),
|
||||
body.toReadPacket(),
|
||||
body.size.toLong().and(0xFFFF_FFFF), // don't use toLongUnsigned: Overload resolution ambiguity
|
||||
@Suppress("INVISIBLE_REFERENCE")
|
||||
net.mamoe.mirai.utils.internal.asReusableInput0(body), // don't use toLongUnsigned: Overload resolution ambiguity
|
||||
"group long message",
|
||||
27
|
||||
)
|
||||
@ -766,13 +760,6 @@ internal abstract class QQAndroidBotBase constructor(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DeprecatedCallableAddReplaceWith")
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING)
|
||||
override suspend fun openChannel(image: Image): ByteReadChannel {
|
||||
return MiraiPlatformUtils.Http.get<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 获取群公告 所需的 bkn 参数
|
||||
* */
|
||||
|
@ -87,6 +87,10 @@ internal class FriendImpl(
|
||||
@JvmSynthetic
|
||||
@OptIn(MiraiInternalAPI::class, ExperimentalStdlibApi::class, ExperimentalTime::class)
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = try {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
if (image.input is net.mamoe.mirai.utils.internal.DeferredReusableInput) {
|
||||
image.input.init(bot.configuration.fileCacheStrategy)
|
||||
}
|
||||
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
|
||||
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
|
||||
}
|
||||
@ -96,14 +100,15 @@ internal class FriendImpl(
|
||||
srcUin = bot.id.toInt(),
|
||||
dstUin = id.toInt(),
|
||||
fileId = 0,
|
||||
fileMd5 = image.md5,
|
||||
fileSize = image.inputSize.toInt(),
|
||||
fileName = image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName,
|
||||
fileMd5 = @Suppress("INVISIBLE_MEMBER") image.md5,
|
||||
fileSize = @Suppress("INVISIBLE_MEMBER")
|
||||
image.input.size.toInt(),
|
||||
fileName = @Suppress("INVISIBLE_MEMBER") image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName,
|
||||
imgOriginal = 1
|
||||
)
|
||||
).sendAndExpect<LongConn.OffPicUp.Response>()
|
||||
|
||||
@Suppress("UNCHECKED_CAST", "DEPRECATION") // bug
|
||||
@Suppress("UNCHECKED_CAST", "DEPRECATION", "INVISIBLE_MEMBER")
|
||||
return when (response) {
|
||||
is LongConn.OffPicUp.Response.FileExists -> net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId)
|
||||
.also {
|
||||
@ -111,7 +116,7 @@ internal class FriendImpl(
|
||||
}
|
||||
is LongConn.OffPicUp.Response.RequireUpload -> {
|
||||
bot.network.logger.verbose {
|
||||
"[Http] Uploading friend image, size=${image.inputSize.sizeToString()}"
|
||||
"[Http] Uploading friend image, size=${image.input.size.sizeToString()}"
|
||||
}
|
||||
|
||||
val time = measureTime {
|
||||
@ -120,13 +125,12 @@ internal class FriendImpl(
|
||||
bot.id,
|
||||
null,
|
||||
imageInput = image.input,
|
||||
inputSize = image.inputSize,
|
||||
uKeyHex = response.uKey.toUHexString("")
|
||||
)
|
||||
}
|
||||
|
||||
bot.network.logger.verbose {
|
||||
"[Http] Uploading friend image: succeed at ${(image.inputSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
||||
"[Http] Uploading friend image: succeed at ${(image.input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
||||
}
|
||||
|
||||
/*
|
||||
@ -151,6 +155,7 @@ internal class FriendImpl(
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
(image.input as? Closeable)?.close()
|
||||
}
|
||||
}
|
@ -109,7 +109,8 @@ internal class GroupImpl(
|
||||
override var name: String
|
||||
get() = _name
|
||||
set(newValue) {
|
||||
checkBotPermissionOperator()
|
||||
|
||||
checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
if (_name != newValue) {
|
||||
val oldValue = _name
|
||||
_name = newValue
|
||||
@ -131,7 +132,7 @@ internal class GroupImpl(
|
||||
override var entranceAnnouncement: String
|
||||
get() = _announcement
|
||||
set(newValue) {
|
||||
checkBotPermissionOperator()
|
||||
checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
if (_announcement != newValue) {
|
||||
val oldValue = _announcement
|
||||
_announcement = newValue
|
||||
@ -152,7 +153,7 @@ internal class GroupImpl(
|
||||
override var isAllowMemberInvite: Boolean
|
||||
get() = _allowMemberInvite
|
||||
set(newValue) {
|
||||
checkBotPermissionOperator()
|
||||
checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
if (_allowMemberInvite != newValue) {
|
||||
val oldValue = _allowMemberInvite
|
||||
_allowMemberInvite = newValue
|
||||
@ -186,7 +187,8 @@ internal class GroupImpl(
|
||||
override var isConfessTalkEnabled: Boolean
|
||||
get() = _confessTalk
|
||||
set(newValue) {
|
||||
checkBotPermissionOperator()
|
||||
|
||||
checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
if (_confessTalk != newValue) {
|
||||
val oldValue = _confessTalk
|
||||
_confessTalk = newValue
|
||||
@ -207,7 +209,8 @@ internal class GroupImpl(
|
||||
override var isMuteAll: Boolean
|
||||
get() = _muteAll
|
||||
set(newValue) {
|
||||
checkBotPermissionOperator()
|
||||
|
||||
checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
if (_muteAll != newValue) {
|
||||
val oldValue = _muteAll
|
||||
_muteAll = newValue
|
||||
@ -403,6 +406,10 @@ internal class GroupImpl(
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@JvmSynthetic
|
||||
override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage = try {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
if (image.input is net.mamoe.mirai.utils.internal.DeferredReusableInput) {
|
||||
image.input.init(bot.configuration.fileCacheStrategy)
|
||||
}
|
||||
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
|
||||
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
|
||||
}
|
||||
@ -412,7 +419,7 @@ internal class GroupImpl(
|
||||
uin = bot.id,
|
||||
groupCode = id,
|
||||
md5 = image.md5,
|
||||
size = image.inputSize
|
||||
size = image.input.size.toInt()
|
||||
).sendAndExpect()
|
||||
|
||||
@Suppress("UNCHECKED_CAST") // bug
|
||||
@ -432,7 +439,7 @@ internal class GroupImpl(
|
||||
bot,
|
||||
response.uploadIpList.zip(response.uploadPortList),
|
||||
response.uKey,
|
||||
image,
|
||||
image.input,
|
||||
kind = "group image",
|
||||
commandId = 2
|
||||
)
|
||||
|
@ -105,7 +105,7 @@ internal class MemberImpl constructor(
|
||||
get() = _nameCard
|
||||
set(newValue) {
|
||||
if (id != bot.id) {
|
||||
group.checkBotPermissionOperator()
|
||||
group.checkBotPermission(MemberPermission.ADMINISTRATOR)
|
||||
}
|
||||
if (_nameCard != newValue) {
|
||||
val oldValue = _nameCard
|
||||
@ -118,7 +118,7 @@ internal class MemberImpl constructor(
|
||||
newValue
|
||||
).sendWithoutExpect()
|
||||
}
|
||||
MemberCardChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast()
|
||||
MemberCardChangeEvent(oldValue, newValue, this@MemberImpl).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -205,6 +205,7 @@ internal class MemberImpl constructor(
|
||||
|
||||
check(response.success) { "kick failed: ${response.ret}" }
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
group.members.delegate.removeIf { it.id == this@MemberImpl.id }
|
||||
MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast()
|
||||
}
|
||||
|
@ -59,17 +59,17 @@ internal fun Contact.logMessageSent(message: Message) {
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
|
||||
internal fun ContactMessage.logMessageReceived() {
|
||||
internal fun MessageEvent.logMessageReceived() {
|
||||
when (this) {
|
||||
is GroupMessage -> bot.logger.verbose {
|
||||
is GroupMessageEvent -> bot.logger.verbose {
|
||||
"[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(${sender.id}) -> ${message.toString()
|
||||
.singleLine()}"
|
||||
}
|
||||
is TempMessage -> bot.logger.verbose {
|
||||
is TempMessageEvent -> bot.logger.verbose {
|
||||
"[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(Temp ${sender.id}) -> ${message.toString()
|
||||
.singleLine()}"
|
||||
}
|
||||
is FriendMessage -> bot.logger.verbose {
|
||||
is FriendMessageEvent -> bot.logger.verbose {
|
||||
"${sender.nick.singleLine()}(${sender.id}) -> ${message.toString().singleLine()}"
|
||||
}
|
||||
}
|
||||
|
@ -358,10 +358,10 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
|
||||
="" a_actionData="" url=""/></msg>
|
||||
*/
|
||||
/**
|
||||
* [JsonMessage]
|
||||
* json?
|
||||
*/
|
||||
1 -> @Suppress("DEPRECATION_ERROR")
|
||||
list.add(JsonMessage(content))
|
||||
list.add(ServiceMessage(1, content))
|
||||
/**
|
||||
* [LongMessage], [ForwardMessage]
|
||||
*/
|
||||
@ -381,7 +381,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
|
||||
else -> {
|
||||
if (element.richMsg.serviceId == 60 || content.startsWith("<?")) {
|
||||
@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))
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network
|
||||
|
||||
import kotlinx.atomicfu.AtomicRef
|
||||
@ -20,7 +22,7 @@ import kotlinx.io.core.use
|
||||
import net.mamoe.mirai.event.*
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
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.UnsupportedSMSLoginException
|
||||
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.utils.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.jvm.JvmField
|
||||
import kotlin.jvm.Volatile
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
@ -204,6 +207,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
|
||||
private val _pendingEnabled = atomic(true)
|
||||
internal val pendingEnabled get() = _pendingEnabled.value
|
||||
|
||||
@JvmField
|
||||
@Volatile
|
||||
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? =
|
||||
LockFreeLinkedList()
|
||||
@ -491,7 +495,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
|
||||
is Packet.NoLog -> {
|
||||
// nothing to do
|
||||
}
|
||||
is ContactMessage -> packet.logMessageReceived()
|
||||
is MessageEvent -> packet.logMessageReceived()
|
||||
is Event -> bot.logger.verbose { "Event: ${packet.toString().singleLine()}" }
|
||||
else -> logger.verbose { "Packet: ${packet.toString().singleLine()}" }
|
||||
}
|
||||
|
@ -77,6 +77,10 @@ internal open class QQAndroidClient(
|
||||
val device: DeviceInfo = SystemDeviceInfo(context),
|
||||
bot: QQAndroidBot
|
||||
) {
|
||||
@Suppress("INVISIBLE_MEMBER")
|
||||
val subAppId: Long
|
||||
get() = bot.configuration.protocol.id
|
||||
|
||||
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
|
||||
|
||||
val keys: Map<String, ByteArray> by lazy {
|
||||
@ -368,7 +372,7 @@ internal class WLoginSigInfo(
|
||||
val deviceToken: ByteArray
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=${psKeyMap.toString()}, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
|
||||
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.highway
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
@ -20,22 +22,24 @@ import io.ktor.utils.io.ByteWriteChannel
|
||||
import kotlinx.coroutines.InternalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.io.ByteReadChannel
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import kotlinx.io.InputStream
|
||||
import kotlinx.io.core.Input
|
||||
import kotlinx.io.core.discardExact
|
||||
import kotlinx.io.core.readAvailable
|
||||
import kotlinx.io.core.use
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
|
||||
import net.mamoe.mirai.qqandroid.utils.*
|
||||
import net.mamoe.mirai.qqandroid.utils.PlatformSocket
|
||||
import net.mamoe.mirai.qqandroid.utils.SocketException
|
||||
import net.mamoe.mirai.qqandroid.utils.addSuppressedMirai
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.utils.io.withUse
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.internal.ReusableInput
|
||||
import net.mamoe.mirai.utils.verbose
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.time.ExperimentalTime
|
||||
@ -47,8 +51,7 @@ internal suspend fun HttpClient.postImage(
|
||||
htcmd: String,
|
||||
uin: Long,
|
||||
groupcode: Long?,
|
||||
imageInput: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor
|
||||
inputSize: Long,
|
||||
imageInput: ReusableInput,
|
||||
uKeyHex: String
|
||||
): Boolean = post<HttpStatusCode> {
|
||||
url {
|
||||
@ -63,7 +66,7 @@ internal suspend fun HttpClient.postImage(
|
||||
|
||||
parameters["term"] = "pc"
|
||||
parameters["ver"] = "5603"
|
||||
parameters["filesize"] = inputSize.toString()
|
||||
parameters["filesize"] = imageInput.size.toString()
|
||||
parameters["range"] = 0.toString()
|
||||
parameters["ukey"] = uKeyHex
|
||||
|
||||
@ -72,63 +75,46 @@ internal suspend fun HttpClient.postImage(
|
||||
|
||||
body = object : OutgoingContent.WriteChannelContent() {
|
||||
override val contentType: ContentType = ContentType.Image.Any
|
||||
override val contentLength: Long = inputSize
|
||||
override val contentLength: Long = imageInput.size
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
override suspend fun writeTo(channel: ByteWriteChannel) {
|
||||
ByteArrayPool.useInstance { buffer: ByteArray ->
|
||||
when (imageInput) {
|
||||
is Input -> {
|
||||
var size: Int
|
||||
while (imageInput.readAvailable(buffer).also { size = it } > 0) {
|
||||
channel.writeFully(buffer, 0, size)
|
||||
channel.flush()
|
||||
}
|
||||
}
|
||||
is ByteReadChannel -> imageInput.copyAndClose(channel)
|
||||
is InputStream -> {
|
||||
var size: Int
|
||||
while (imageInput.read(buffer).also { size = it } > 0) {
|
||||
channel.writeFully(buffer, 0, size)
|
||||
channel.flush()
|
||||
}
|
||||
}
|
||||
else -> error("unsupported imageInput: ${imageInput::class.simpleName}")
|
||||
}
|
||||
}
|
||||
imageInput.writeTo(channel)
|
||||
|
||||
}
|
||||
}
|
||||
} == HttpStatusCode.OK
|
||||
|
||||
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
|
||||
internal object HighwayHelper {
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
suspend fun uploadImageToServers(
|
||||
bot: QQAndroidBot,
|
||||
servers: List<Pair<Int, Int>>,
|
||||
uKey: ByteArray,
|
||||
image: ExternalImage,
|
||||
image: ReusableInput,
|
||||
kind: String,
|
||||
commandId: Int
|
||||
) = uploadImageToServers(bot, servers, uKey, image.md5, image.input, image.inputSize, kind, commandId)
|
||||
) = uploadImageToServers(bot, servers, uKey, image.md5, image, kind, commandId)
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
@OptIn(ExperimentalTime::class)
|
||||
suspend fun uploadImageToServers(
|
||||
bot: QQAndroidBot,
|
||||
servers: List<Pair<Int, Int>>,
|
||||
uKey: ByteArray,
|
||||
md5: ByteArray,
|
||||
input: Any,
|
||||
inputSize: Long,
|
||||
input: ReusableInput,
|
||||
kind: String,
|
||||
commandId: Int
|
||||
) = servers.retryWithServers(
|
||||
(inputSize * 1000 / 1024 / 10).coerceAtLeast(5000),
|
||||
(input.size * 1000 / 1024 / 10).coerceAtLeast(5000),
|
||||
onFail = {
|
||||
throw IllegalStateException("cannot upload $kind, failed on all servers.", it)
|
||||
}
|
||||
) { ip, port ->
|
||||
bot.network.logger.verbose {
|
||||
"[Highway] Uploading $kind to ${ip}:$port, size=${inputSize.sizeToString()}"
|
||||
"[Highway] Uploading $kind to ${ip}:$port, size=${input.size.sizeToString()}"
|
||||
}
|
||||
|
||||
val time = measureTime {
|
||||
@ -137,7 +123,6 @@ internal object HighwayHelper {
|
||||
serverIp = ip,
|
||||
serverPort = port,
|
||||
imageInput = input,
|
||||
inputSize = inputSize.toInt(),
|
||||
fileMd5 = md5,
|
||||
ticket = uKey,
|
||||
commandId = commandId
|
||||
@ -145,22 +130,21 @@ internal object HighwayHelper {
|
||||
}
|
||||
|
||||
bot.network.logger.verbose {
|
||||
"[Highway] Uploading $kind: succeed at ${(inputSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
||||
"[Highway] Uploading $kind: succeed at ${(input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
|
||||
@OptIn(InternalCoroutinesApi::class)
|
||||
internal suspend fun uploadImage(
|
||||
client: QQAndroidClient,
|
||||
serverIp: String,
|
||||
serverPort: Int,
|
||||
ticket: ByteArray,
|
||||
imageInput: Any,
|
||||
inputSize: Int,
|
||||
imageInput: ReusableInput,
|
||||
fileMd5: ByteArray,
|
||||
commandId: Int // group=2, friend=1
|
||||
) {
|
||||
require(imageInput is Input || imageInput is InputStream || imageInput is ByteReadChannel) { "unsupported imageInput: ${imageInput::class.simpleName}" }
|
||||
require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" }
|
||||
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
|
||||
// require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
|
||||
@ -177,13 +161,14 @@ internal object HighwayHelper {
|
||||
socket.use {
|
||||
createImageDataPacketSequence(
|
||||
client = client,
|
||||
appId = client.subAppId.toInt(),
|
||||
command = "PicUp.DataUp",
|
||||
commandId = commandId,
|
||||
ticket = ticket,
|
||||
data = imageInput,
|
||||
dataSize = inputSize,
|
||||
fileMd5 = fileMd5
|
||||
).collect {
|
||||
).withUse {
|
||||
flow.collect {
|
||||
socket.send(it)
|
||||
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
|
||||
|
||||
@ -198,6 +183,7 @@ internal object HighwayHelper {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
internal suspend inline fun List<Pair<Int, Int>>.retryWithServers(
|
||||
|
@ -11,12 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.highway
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.io.ByteReadChannel
|
||||
import kotlinx.io.InputStream
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.io.core.Input
|
||||
import kotlinx.io.core.buildPacket
|
||||
import kotlinx.io.core.writeFully
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
@ -25,41 +20,34 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
|
||||
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
|
||||
import net.mamoe.mirai.qqandroid.utils.io.chunkedFlow
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.internal.ChunkedFlowSession
|
||||
import net.mamoe.mirai.utils.internal.ChunkedInput
|
||||
import net.mamoe.mirai.utils.internal.ReusableInput
|
||||
import net.mamoe.mirai.utils.internal.map
|
||||
|
||||
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
|
||||
internal fun createImageDataPacketSequence(
|
||||
// RequestDataTrans
|
||||
client: QQAndroidClient,
|
||||
command: String,
|
||||
appId: Int = 537062845,
|
||||
appId: Int,
|
||||
dataFlag: Int = 4096,
|
||||
commandId: Int,
|
||||
localId: Int = 2052,
|
||||
ticket: ByteArray,
|
||||
|
||||
data: Any,
|
||||
dataSize: Int,
|
||||
data: ReusableInput,
|
||||
fileMd5: ByteArray,
|
||||
sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE
|
||||
): Flow<ByteReadPacket> {
|
||||
): ChunkedFlowSession<ByteReadPacket> {
|
||||
ByteArrayPool.checkBufferSize(sizePerPacket)
|
||||
require(data is Input || data is InputStream || data is ByteReadChannel) { "unsupported data: ${data::class.simpleName}" }
|
||||
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
|
||||
require(data !is ByteReadPacket || data.remaining.toInt() == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as ByteReadPacket).remaining}" }
|
||||
|
||||
val flow = when (data) {
|
||||
is ByteReadPacket -> data.chunkedFlow(sizePerPacket)
|
||||
is Input -> data.chunkedFlow(sizePerPacket)
|
||||
is ByteReadChannel -> data.chunkedFlow(sizePerPacket)
|
||||
is InputStream -> data.chunkedFlow(sizePerPacket)
|
||||
else -> error("unreachable code")
|
||||
}
|
||||
val session: ChunkedFlowSession<ChunkedInput> = data.chunkedFlow(sizePerPacket)
|
||||
|
||||
var offset = 0L
|
||||
return flow.map { chunkedInput ->
|
||||
return session.map { chunkedInput ->
|
||||
buildPacket {
|
||||
val head = CSDataHighwayHead.ReqDataHighwayHead(
|
||||
msgBasehead = CSDataHighwayHead.DataHighwayHead(
|
||||
@ -82,7 +70,7 @@ internal fun createImageDataPacketSequence(
|
||||
// cacheAddr = 812157193,
|
||||
datalength = chunkedInput.bufferSize,
|
||||
dataoffset = offset,
|
||||
filesize = dataSize.toLong(),
|
||||
filesize = data.size,
|
||||
serviceticket = ticket,
|
||||
md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
|
||||
fileMd5 = fileMd5,
|
||||
|
@ -10,9 +10,9 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -10,8 +10,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -1,9 +1,9 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -10,8 +10,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
internal class OnlinePushPack {
|
||||
|
@ -10,10 +10,10 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
|
@ -1,8 +1,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -10,9 +10,9 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
private val EMPTY_MAP = mapOf<String, String>()
|
||||
|
@ -10,8 +10,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -10,8 +10,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -10,8 +10,8 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
@Serializable
|
||||
|
@ -197,6 +197,7 @@ internal object KnownPacketFactories {
|
||||
|
||||
readString(readInt() - 4)// uinAccount
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
ByteArrayPool.useInstance(this.remaining.toInt()) { data ->
|
||||
val size = this.readAvailable(data)
|
||||
|
||||
@ -219,6 +220,7 @@ internal object KnownPacketFactories {
|
||||
it as IncomingPacket<T>
|
||||
|
||||
if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
bot.network.pendingIncomingPackets?.addLast(it.also {
|
||||
it.consumer = consumer
|
||||
it.flag2 = flag2
|
||||
@ -384,6 +386,7 @@ internal object KnownPacketFactories {
|
||||
}
|
||||
0 -> {
|
||||
val data = if (bot.client.loginState == 0) {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
ByteArrayPool.useInstance(this.remaining.toInt()) { byteArrayBuffer ->
|
||||
val size = (this.remaining - 1).toInt()
|
||||
this.readFully(byteArrayBuffer, 0, size)
|
||||
|
@ -83,7 +83,7 @@ internal fun BytePacketBuilder.t18(
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
internal fun BytePacketBuilder.t106(
|
||||
appId: Long = 16L,
|
||||
subAppId: Long = 537062845L,
|
||||
subAppId: Long,
|
||||
appClientVersion: Int = 0,
|
||||
uin: Long,
|
||||
n5_always_1: Int = 1,
|
||||
@ -159,7 +159,7 @@ internal fun BytePacketBuilder.t116(
|
||||
|
||||
internal fun BytePacketBuilder.t100(
|
||||
appId: Long = 16,
|
||||
subAppId: Long = 537062845,
|
||||
subAppId: Long,
|
||||
appClientVersion: Int
|
||||
) {
|
||||
writeShort(0x100)
|
||||
|
@ -100,7 +100,7 @@ internal class TroopManagement {
|
||||
serviceType = 7,
|
||||
result = 0,
|
||||
bodybuffer = Oidb0x88d.ReqBody(
|
||||
appid = 537062845,
|
||||
appid = client.subAppId.toInt(),
|
||||
stzreqgroupinfo = listOf(
|
||||
Oidb0x88d.ReqGroupInfo(
|
||||
stgroupinfo = Oidb0x88d.GroupInfo(
|
||||
|
@ -17,6 +17,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x388
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.toLongUnsigned
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
|
||||
import kotlin.random.Random
|
||||
@ -27,9 +28,6 @@ internal fun getRandomString(length: Int): String =
|
||||
|
||||
private val defaultRanges: Array<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 =
|
||||
String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() })
|
||||
|
||||
@ -41,7 +39,7 @@ internal class ImgStore {
|
||||
uin: Long,
|
||||
groupCode: Long,
|
||||
md5: ByteArray,
|
||||
size: Long,
|
||||
size: Int,
|
||||
picWidth: Int = 0, // not orthodox
|
||||
picHeight: Int = 0, // not orthodox
|
||||
picType: Int = 1000,
|
||||
@ -63,7 +61,7 @@ internal class ImgStore {
|
||||
groupCode = groupCode,
|
||||
srcUin = uin,
|
||||
fileMd5 = md5,
|
||||
fileSize = size,
|
||||
fileSize = size.toLongUnsigned(),
|
||||
fileId = fileId,
|
||||
fileName = filename,
|
||||
picWidth = picWidth,
|
||||
|
@ -30,8 +30,8 @@ import net.mamoe.mirai.event.events.BotJoinGroupEvent
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
import net.mamoe.mirai.event.events.MemberJoinEvent
|
||||
import net.mamoe.mirai.getFriendOrNull
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.TempMessage
|
||||
import net.mamoe.mirai.message.FriendMessageEvent
|
||||
import net.mamoe.mirai.message.TempMessageEvent
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.PttMessage
|
||||
import net.mamoe.mirai.message.data.Voice
|
||||
@ -228,6 +228,7 @@ internal class MessageSvc {
|
||||
// 新群
|
||||
|
||||
val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
bot.groups.delegate.addLast(newGroup)
|
||||
return@mapNotNull BotJoinGroupEvent(newGroup)
|
||||
} else {
|
||||
@ -237,6 +238,7 @@ internal class MessageSvc {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
|
||||
.also { group.members.delegate.addLast(it) })
|
||||
}
|
||||
@ -250,6 +252,7 @@ internal class MessageSvc {
|
||||
if (group.members.contains(msg.msgHead.authUin)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
|
||||
.also { group.members.delegate.addLast(it) })
|
||||
}
|
||||
@ -276,7 +279,7 @@ internal class MessageSvc {
|
||||
friend.lastMessageSequence.loop { instant ->
|
||||
if (msg.msgHead.msgSeq > instant) {
|
||||
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
|
||||
return@mapNotNull FriendMessage(
|
||||
return@mapNotNull FriendMessageEvent(
|
||||
friend,
|
||||
msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true),
|
||||
msg.msgHead.msgTime
|
||||
@ -307,7 +310,7 @@ internal class MessageSvc {
|
||||
member.lastMessageSequence.loop { instant ->
|
||||
if (msg.msgHead.msgSeq > instant) {
|
||||
if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
|
||||
return@mapNotNull TempMessage(
|
||||
return@mapNotNull TempMessageEvent(
|
||||
member,
|
||||
msg.toMessageChain(
|
||||
bot,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
|
||||
|
||||
@ -23,7 +23,7 @@ import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.getFriendOrNull
|
||||
import net.mamoe.mirai.getGroupOrNull
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.message.GroupMessageEvent
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.contact.*
|
||||
import net.mamoe.mirai.qqandroid.message.contextualBugReportException
|
||||
@ -87,7 +87,8 @@ internal class OnlinePush {
|
||||
}
|
||||
}
|
||||
|
||||
val group = bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
|
||||
val group =
|
||||
bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
|
||||
val sender = if (anonymous != null) {
|
||||
group.newAnonymous(anonymous.anonNick.encodeToString())
|
||||
} else {
|
||||
@ -108,12 +109,12 @@ internal class OnlinePush {
|
||||
}
|
||||
|
||||
val flags = extraInfo?.flags ?: 0
|
||||
return GroupMessage(
|
||||
return GroupMessageEvent(
|
||||
senderName = name.also {
|
||||
if (it != sender.nameCard) {
|
||||
val origin = sender._nameCard
|
||||
sender._nameCard = name
|
||||
MemberCardChangeEvent(origin, name, sender, sender).broadcast() // 不知道operator
|
||||
MemberCardChangeEvent(origin, name, sender).broadcast()
|
||||
}
|
||||
},
|
||||
sender = sender,
|
||||
@ -562,7 +563,7 @@ internal class OnlinePush {
|
||||
if (new == old) return@mapNotNull null
|
||||
member._nameCard = new
|
||||
|
||||
return@mapNotNull MemberCardChangeEvent(old, new, member, null)
|
||||
return@mapNotNull MemberCardChangeEvent(old, new, member)
|
||||
}
|
||||
2 -> {
|
||||
if (info.value.singleOrNull()?.toInt() != 0) {
|
||||
|
@ -24,11 +24,8 @@ import net.mamoe.mirai.qqandroid.utils.io.serialization.jceRequestSBuffer
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeJceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.toByteArray
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
|
||||
internal class ProfileService {
|
||||
|
||||
@SinceMirai("0.37.0")
|
||||
object GroupMngReq : OutgoingPacketFactory<GroupMngReq.GroupMngReqResponse>("ProfileService.GroupMngReq") {
|
||||
data class GroupMngReqResponse(val errorCode: Int, val errorMessage: String) : Packet
|
||||
|
||||
|
@ -70,6 +70,7 @@ internal class ConfigPushSvc {
|
||||
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse? {
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
ByteArrayPool.useInstance(this.remaining.toInt()) { buffer ->
|
||||
val length = this.readAvailable(buffer)
|
||||
|
||||
|
@ -25,7 +25,7 @@ internal class Heartbeat {
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient
|
||||
): OutgoingPacket = buildLoginOutgoingPacket(client, 0, key = NO_ENCRYPT) {
|
||||
writeSsoPacket(client, 537062845, commandName, sequenceId = it) {
|
||||
writeSsoPacket(client, client.subAppId, commandName, sequenceId = it) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
|
||||
|
||||
import kotlinx.io.core.ByteReadPacket
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import net.mamoe.mirai.event.events.BotOfflineEvent
|
||||
import net.mamoe.mirai.qqandroid.QQAndroidBot
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
@ -88,8 +89,6 @@ internal class StatSvc {
|
||||
override fun toString(): String = "Response(StatSvc.register)"
|
||||
}
|
||||
|
||||
private const val subAppId = 537062845L
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
@ -101,7 +100,7 @@ internal class StatSvc {
|
||||
key = client.wLoginSigInfo.d2Key
|
||||
) { sequenceId ->
|
||||
writeSsoPacket(
|
||||
client, subAppId = subAppId, commandName = commandName,
|
||||
client, subAppId = client.subAppId, commandName = commandName,
|
||||
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
|
||||
) {
|
||||
writeJceStruct(
|
||||
@ -153,7 +152,7 @@ internal class StatSvc {
|
||||
var44.strVendorName = ROMUtil.getRomName();
|
||||
var44.strVendorOSName = ROMUtil.getRomVersion(20);
|
||||
*/
|
||||
bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump(
|
||||
bytes_0x769_reqbody = ProtoBuf.dump(
|
||||
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
|
||||
rpt_config_list = listOf(
|
||||
Oidb0x769.ConfigSeq(
|
||||
|
@ -32,8 +32,6 @@ internal class WtLogin {
|
||||
@Suppress("FunctionName")
|
||||
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
|
||||
internal object Login : OutgoingPacketFactory<Login.LoginPacketResponse>("wtlogin.login") {
|
||||
private const val subAppId = 537062845L
|
||||
|
||||
/**
|
||||
* 提交验证码
|
||||
*/
|
||||
@ -42,7 +40,7 @@ internal class WtLogin {
|
||||
client: QQAndroidClient,
|
||||
ticket: String
|
||||
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
|
||||
writeShort(2) // subCommand
|
||||
writeShort(4) // count of TLVs
|
||||
@ -59,7 +57,7 @@ internal class WtLogin {
|
||||
captchaSign: ByteArray,
|
||||
captchaAnswer: String
|
||||
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
|
||||
writeShort(2) // subCommand
|
||||
writeShort(4) // count of TLVs
|
||||
@ -78,7 +76,7 @@ internal class WtLogin {
|
||||
client: QQAndroidClient,
|
||||
t402: ByteArray
|
||||
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
|
||||
writeShort(20) // subCommand
|
||||
writeShort(4) // count of TLVs, probably ignored by server?
|
||||
@ -100,7 +98,7 @@ internal class WtLogin {
|
||||
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
writeSsoPacket(
|
||||
client,
|
||||
subAppId,
|
||||
client.subAppId,
|
||||
commandName,
|
||||
sequenceId = sequenceId,
|
||||
unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00"
|
||||
@ -126,13 +124,12 @@ internal class WtLogin {
|
||||
*/
|
||||
object SubCommand9 {
|
||||
private const val appId = 16L
|
||||
private const val subAppId = 537062845L
|
||||
|
||||
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient
|
||||
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
|
||||
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
|
||||
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
|
||||
writeShort(9) // subCommand
|
||||
writeShort(17) // count of TLVs, probably ignored by server?
|
||||
@ -142,7 +139,7 @@ internal class WtLogin {
|
||||
t1(client.uin, client.device.ipAddress)
|
||||
t106(
|
||||
appId,
|
||||
subAppId /* maybe 1*/,
|
||||
client.subAppId /* maybe 1*/,
|
||||
client.appClientVersion,
|
||||
client.uin,
|
||||
1,
|
||||
@ -166,7 +163,7 @@ internal class WtLogin {
|
||||
if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
|
||||
*/
|
||||
t116(client.miscBitMap, client.subSigMap)
|
||||
t100(appId, subAppId, client.appClientVersion)
|
||||
t100(appId, client.subAppId, client.appClientVersion)
|
||||
t107(0)
|
||||
|
||||
// t108(byteArrayOf())
|
||||
|
@ -1,41 +1,9 @@
|
||||
package net.mamoe.mirai.qqandroid.utils
|
||||
|
||||
import kotlinx.io.pool.DefaultPool
|
||||
import kotlinx.io.pool.ObjectPool
|
||||
|
||||
/**
|
||||
* 缓存 [ByteArray] 实例的 [ObjectPool]
|
||||
*/
|
||||
internal object ByteArrayPool : DefaultPool<ByteArray>(256) {
|
||||
/**
|
||||
* 每一个 [ByteArray] 的大小
|
||||
*/
|
||||
const val BUFFER_SIZE: Int = 8192 * 8
|
||||
|
||||
override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE)
|
||||
|
||||
override fun clearInstance(instance: ByteArray): ByteArray = instance
|
||||
|
||||
fun checkBufferSize(size: Int) {
|
||||
require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" }
|
||||
}
|
||||
|
||||
fun checkBufferSize(size: Long) {
|
||||
require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" }
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求一个大小至少为 [requestedSize] 的 [ByteArray] 实例.
|
||||
*/ // 不要写为扩展函数. 它需要优先于 kotlinx.io 的扩展函数 resolve
|
||||
inline fun <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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
internal typealias ByteArrayPool = net.mamoe.mirai.utils.internal.ByteArrayPool
|
@ -27,6 +27,7 @@ import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
internal inline fun <R> ByteReadPacket.useBytes(
|
||||
n: Int = remaining.toInt(),//not that safe but adequate
|
||||
block: (data: ByteArray, length: Int) -> R
|
||||
|
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -23,10 +23,13 @@ import kotlinx.serialization.modules.SerialModule
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import kotlinx.serialization.protobuf.ProtoNumberType
|
||||
import kotlinx.serialization.protobuf.ProtoType
|
||||
import moe.him188.jcekt.JceId
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.ProtoBufWithNullableSupport.Varint.encodeVarint
|
||||
|
||||
internal typealias ProtoDesc = Pair<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 {
|
||||
val idx = getSerialId(desc, index) ?: (if (zeroBasedDefault) index else index + 1)
|
||||
val format = desc.findAnnotation<ProtoType>(index)?.type
|
||||
|
@ -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`
|
@ -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
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -16,15 +16,14 @@ import kotlinx.io.core.*
|
||||
import kotlinx.serialization.DeserializationStrategy
|
||||
import kotlinx.serialization.SerialDescriptor
|
||||
import kotlinx.serialization.SerializationStrategy
|
||||
import moe.him188.jcekt.Jce
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
|
||||
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
|
||||
import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
|
||||
import net.mamoe.mirai.qqandroid.utils.io.readPacketExact
|
||||
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce
|
||||
import net.mamoe.mirai.qqandroid.utils.read
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
@ -34,25 +33,21 @@ internal fun <T : JceStruct> ByteArray.loadWithUniPacket(
|
||||
): T = this.read { readUniPacket(deserializer, name) }
|
||||
|
||||
internal fun <T : JceStruct> ByteArray.loadAs(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
c: JceCharset = JceCharset.UTF8
|
||||
): T = this.read { Jce.byCharSet(c).load(deserializer, this) }
|
||||
deserializer: DeserializationStrategy<T>
|
||||
): T = this.read { Jce.UTF_8.load(deserializer, this) }
|
||||
|
||||
internal fun <T : JceStruct> BytePacketBuilder.writeJceStruct(
|
||||
serializer: SerializationStrategy<T>,
|
||||
struct: T,
|
||||
charset: JceCharset = JceCharset.UTF8
|
||||
struct: T
|
||||
) {
|
||||
Jce.byCharSet(charset).dumpTo(serializer, struct, this)
|
||||
Jce.UTF_8.dumpTo(serializer, struct, this)
|
||||
}
|
||||
|
||||
internal fun <T : JceStruct> ByteReadPacket.readJceStruct(
|
||||
serializer: DeserializationStrategy<T>,
|
||||
charset: JceCharset = JceCharset.UTF8,
|
||||
length: Int = this.remaining.toInt()
|
||||
): T {
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
return Jce.byCharSet(charset).load(serializer, this.readPacketExact(length))
|
||||
return Jce.UTF_8.load(serializer, this.readPacketExact(length))
|
||||
}
|
||||
|
||||
/**
|
||||
@ -103,9 +98,8 @@ private fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String
|
||||
}
|
||||
|
||||
internal fun <T : JceStruct> T.toByteArray(
|
||||
serializer: SerializationStrategy<T>,
|
||||
c: JceCharset = JceCharset.UTF8
|
||||
): ByteArray = Jce.byCharSet(c).dump(serializer, this)
|
||||
serializer: SerializationStrategy<T>
|
||||
): ByteArray = Jce.UTF_8.dump(serializer, this)
|
||||
|
||||
internal fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
|
||||
this.writeFully(v.toByteArray(serializer))
|
||||
@ -140,19 +134,12 @@ internal fun <T : JceStruct> jceRequestSBuffer(
|
||||
name: String,
|
||||
serializer: SerializationStrategy<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 {
|
||||
return RequestDataVersion3(
|
||||
mapOf(
|
||||
name to JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0
|
||||
)
|
||||
).toByteArray(RequestDataVersion3.serializer(), charset)
|
||||
).toByteArray(RequestDataVersion3.serializer())
|
||||
}
|
||||
|
||||
private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A)
|
||||
|
File diff suppressed because one or more lines are too long
@ -42,7 +42,6 @@ actual abstract class Group : Contact(), CoroutineScope {
|
||||
/**
|
||||
* 群设置
|
||||
*/
|
||||
@SinceMirai("0.30.0")
|
||||
actual abstract val settings: GroupSettings
|
||||
|
||||
/**
|
||||
|
@ -15,10 +15,8 @@ package net.mamoe.mirai
|
||||
import kotlinx.coroutines.CoroutineName
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.io.ByteReadChannel
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.data.AddFriendResult
|
||||
import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
|
||||
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
|
||||
import net.mamoe.mirai.event.events.NewFriendRequestEvent
|
||||
@ -29,7 +27,6 @@ import net.mamoe.mirai.network.LoginFailedException
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmStatic
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
@ -48,6 +45,8 @@ suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
|
||||
*
|
||||
* @see Contact 联系人
|
||||
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
|
||||
*
|
||||
* @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式.
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
|
||||
@ -56,6 +55,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
/**
|
||||
* 复制一份此时的 [Bot] 实例列表.
|
||||
*/
|
||||
@PlannedRemoval("1.2.0")
|
||||
@Deprecated("use botInstances instead", replaceWith = ReplaceWith("botInstances"))
|
||||
@JvmStatic
|
||||
val instances: List<WeakRef<Bot>>
|
||||
@ -64,7 +64,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
/**
|
||||
* 复制一份此时的 [Bot] 实例列表.
|
||||
*/
|
||||
@SinceMirai("0.39.1")
|
||||
@JvmStatic
|
||||
val botInstances: List<Bot>
|
||||
get() = BotImpl.instances.asSequence().mapNotNull { it.get() }.toList()
|
||||
@ -72,7 +71,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
/**
|
||||
* 遍历每一个 [Bot] 实例
|
||||
*/
|
||||
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
|
||||
fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
|
||||
|
||||
/**
|
||||
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
|
||||
@ -89,20 +88,14 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
*/
|
||||
abstract val context: Context
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("use id instead", replaceWith = ReplaceWith("id"))
|
||||
abstract val uin: Long
|
||||
|
||||
/**
|
||||
* QQ 号码. 实际类型为 uint
|
||||
*/
|
||||
@SinceMirai("0.32.0")
|
||||
abstract override val id: Long
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
@SinceMirai("0.33.1")
|
||||
abstract val nick: String
|
||||
|
||||
/**
|
||||
@ -113,7 +106,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
// region contacts
|
||||
|
||||
/**
|
||||
* [QQ.id] 与 [Bot.uin] 相同的 [_lowLevelNewFriend] 实例
|
||||
* [User.id] 与 [Bot.id] 相同的 [_lowLevelNewFriend] 实例
|
||||
*/
|
||||
abstract val selfQQ: Friend
|
||||
|
||||
@ -179,6 +172,8 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
|
||||
/**
|
||||
* 获取图片下载链接
|
||||
*
|
||||
* @see Image.queryUrl [Image] 的扩展函数
|
||||
*/
|
||||
@JvmSynthetic
|
||||
abstract suspend fun queryImageUrl(image: Image): String
|
||||
@ -193,7 +188,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
* @param targetUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.39.0")
|
||||
abstract fun constructMessageSource(
|
||||
kind: OfflineMessageSource.Kind,
|
||||
fromUin: Long, targetUin: Long,
|
||||
@ -201,35 +195,12 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
originalMessage: MessageChain
|
||||
): OfflineMessageSource
|
||||
|
||||
/**
|
||||
* 获取图片下载链接并开始下载.
|
||||
*
|
||||
* @see ByteReadChannel.copyAndClose
|
||||
* @see ByteReadChannel.copyTo
|
||||
*/
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING)
|
||||
@MiraiExperimentalAPI
|
||||
@JvmSynthetic
|
||||
abstract suspend fun openChannel(image: Image): ByteReadChannel
|
||||
|
||||
/**
|
||||
* 添加一个好友
|
||||
*
|
||||
* @param message 若需要验证请求时的验证消息.
|
||||
* @param remark 好友备注
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@MiraiExperimentalAPI("未支持")
|
||||
abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult
|
||||
|
||||
|
||||
/**
|
||||
* 通过好友验证
|
||||
*
|
||||
* @param event 好友验证的事件对象
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent)
|
||||
|
||||
@ -239,7 +210,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
* @param event 好友验证的事件对象
|
||||
* @param blackList 拒绝后是否拉入黑名单
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false)
|
||||
|
||||
@ -248,7 +218,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
*
|
||||
* @param event 加群验证的事件对象
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent)
|
||||
|
||||
@ -258,7 +227,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
* @param event 加群验证的事件对象
|
||||
* @param blackList 拒绝后是否拉入黑名单
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
|
||||
|
||||
@ -268,7 +236,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
* @param event 加群验证的事件对象
|
||||
* @param blackList 忽略后是否拉入黑名单
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
|
||||
|
||||
@ -277,7 +244,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
*
|
||||
* @param event 邀请入群的事件对象
|
||||
*/
|
||||
@SinceMirai("0.39.4")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
|
||||
|
||||
@ -287,7 +253,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
* @param event 邀请入群的事件对象
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.39.4")
|
||||
abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
|
||||
|
||||
// endregion
|
||||
@ -313,19 +278,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
abstract val network: BotNetworkHandler
|
||||
|
||||
@PlannedRemoval("1.0.0.")
|
||||
@get:JvmName("getSelfQQ")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
val selfQQDeprecated: QQ
|
||||
get() = selfQQ
|
||||
|
||||
@PlannedRemoval("1.0.0.")
|
||||
@JvmName("getFriend")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
fun getFriendDeprecated(id: Long): QQ = this.getFriend(id)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -14,15 +14,16 @@ package net.mamoe.mirai
|
||||
import net.mamoe.mirai.utils.BotConfiguration
|
||||
import net.mamoe.mirai.utils.Context
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
* 构造 [Bot] 的工厂.
|
||||
* 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式.
|
||||
*
|
||||
* 在协议模块中有各自的实现.
|
||||
* - `mirai-core-timpc`: `TIMPC`
|
||||
* - `mirai-core-qqandroid`: `QQAndroid`
|
||||
* `mirai-core-qqandroid`: `QQAndroid`
|
||||
*
|
||||
* 在 JVM, 请查看 `BotFactoryJvm`
|
||||
*/
|
||||
interface BotFactory {
|
||||
expect interface BotFactory {
|
||||
/**
|
||||
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
|
||||
*/
|
||||
@ -49,6 +50,7 @@ interface BotFactory {
|
||||
/**
|
||||
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun BotFactory.Bot(
|
||||
context: Context,
|
||||
qq: Long,
|
||||
@ -59,6 +61,7 @@ inline fun BotFactory.Bot(
|
||||
/**
|
||||
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
|
||||
*/
|
||||
@JvmSynthetic
|
||||
inline fun BotFactory.Bot(
|
||||
context: Context,
|
||||
qq: Long,
|
||||
|
@ -24,6 +24,8 @@ import net.mamoe.mirai.network.closeAndJoin
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.internal.retryCatching
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTime
|
||||
|
||||
/*
|
||||
* 泛型 N 不需要向外(接口)暴露.
|
||||
@ -46,10 +48,6 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
|
||||
override val context: Context by context.unsafeWeakRef()
|
||||
|
||||
@Deprecated("use id instead", replaceWith = ReplaceWith("id"))
|
||||
override val uin: Long
|
||||
get() = this.id
|
||||
|
||||
final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) }
|
||||
|
||||
init {
|
||||
@ -60,7 +58,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
@PublishedApi
|
||||
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)
|
||||
}
|
||||
|
||||
@ -90,6 +88,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
@Throws(LoginFailedException::class) // only
|
||||
protected abstract suspend fun relogin(cause: Throwable?)
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
@Suppress("unused")
|
||||
private val offlineListener: Listener<BotOfflineEvent> =
|
||||
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" }
|
||||
|
||||
val time = measureTime {
|
||||
tailrec suspend fun reconnect() {
|
||||
retryCatching<Unit>(configuration.reconnectionRetryTimes,
|
||||
except = LoginFailedException::class) { tryCount, _ ->
|
||||
@ -120,7 +120,6 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
|
||||
relogin((event as? BotOfflineEvent.Dropped)?.cause)
|
||||
}
|
||||
logger.info { "Reconnected successfully" }
|
||||
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
|
||||
return
|
||||
}.getOrElse {
|
||||
@ -136,9 +135,11 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
|
||||
}
|
||||
reconnect()
|
||||
}
|
||||
|
||||
reconnect()
|
||||
}
|
||||
|
||||
logger.info { "Reconnected successfully in ${time.inMilliseconds} ms" }
|
||||
}
|
||||
is BotOfflineEvent.Active -> {
|
||||
val msg = if (event.cause == null) {
|
||||
""
|
||||
|
@ -32,11 +32,11 @@ import kotlin.jvm.JvmSynthetic
|
||||
|
||||
|
||||
/**
|
||||
* 联系人. 虽然叫做联系人, 但他的子类有 [用户][User], 和 [群][Group].
|
||||
* 联系对象, 即可以与 [Bot] 互动的对象. 包含 [用户][User], 和 [群][Group].
|
||||
*/
|
||||
abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot {
|
||||
/**
|
||||
* 这个联系人所属 [Bot].
|
||||
* 这个联系对象所属 [Bot].
|
||||
*/
|
||||
@WeakRefProperty
|
||||
abstract val bot: Bot
|
||||
@ -44,10 +44,7 @@ abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot
|
||||
/**
|
||||
* 可以是 QQ 号码或者群号码.
|
||||
*
|
||||
* 对于 [QQ], `uin` 与 `id` 是相同的意思.
|
||||
* 对于 [Group], `groupCode` 与 `id` 是相同的意思.
|
||||
*
|
||||
* @see QQ.id
|
||||
* @see User.id
|
||||
* @see Group.id
|
||||
*/
|
||||
abstract override val id: Long
|
||||
|
@ -11,8 +11,9 @@
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.jvm.JvmName
|
||||
import net.mamoe.mirai.utils.LockFreeLinkedList
|
||||
import net.mamoe.mirai.utils.asSequence
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
|
||||
/**
|
||||
@ -20,9 +21,8 @@ import kotlin.jvm.JvmName
|
||||
*
|
||||
* @see ContactList.asSequence
|
||||
*/
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Suppress("unused")
|
||||
class ContactList<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> {
|
||||
operator fun get(id: Long): C =
|
||||
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> {
|
||||
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
|
||||
get() = "[" + @OptIn(MiraiInternalAPI::class) buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(
|
||||
get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(
|
||||
2
|
||||
) + "]"
|
||||
|
||||
|
||||
@MiraiInternalAPI
|
||||
operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
|
||||
internal operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
|
||||
forEach { if (it.id == id) return it }
|
||||
throw NoSuchElementException("No such contact: $id")
|
||||
}
|
||||
|
||||
@MiraiInternalAPI
|
||||
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
|
||||
internal fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
|
||||
forEach { if (it.id == id) return it }
|
||||
return null
|
||||
}
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated(
|
||||
"use firstOrNull from stdlib",
|
||||
replaceWith = ReplaceWith("this.asSequence().firstOrNull(filter)"),
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
inline fun <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()
|
@ -10,7 +10,6 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
|
||||
/**
|
||||
* 拥有 [id] 的对象.
|
||||
@ -19,7 +18,6 @@ import net.mamoe.mirai.utils.SinceMirai
|
||||
* @see Contact
|
||||
* @see Bot
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
interface ContactOrBot {
|
||||
/**
|
||||
* QQ 号或群号.
|
||||
|
@ -12,14 +12,12 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
|
||||
/**
|
||||
* 发送消息时消息过长抛出的异常.
|
||||
*
|
||||
* @see Contact.sendMessage
|
||||
*/
|
||||
@SinceMirai("0.32.0")
|
||||
class MessageTooLargeException(
|
||||
val target: Contact,
|
||||
/**
|
||||
@ -38,7 +36,6 @@ class MessageTooLargeException(
|
||||
*
|
||||
* @see Group.sendMessage
|
||||
*/
|
||||
@SinceMirai("0.33.0")
|
||||
class BotIsBeingMutedException(
|
||||
val target: Group
|
||||
) : RuntimeException("bot is being muted, remaining ${target.botMuteRemaining} seconds")
|
@ -16,21 +16,23 @@ import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.FriendMessageEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.toMessage
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
* 好友 对象.
|
||||
* 注意: 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot].
|
||||
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
|
||||
* 代表一位好友.
|
||||
*
|
||||
* 对于同一个 [Bot] 任何一个人的 [Friend] 实例都是单一的.
|
||||
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
|
||||
* 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot].
|
||||
* 对于同一个 [Bot], 任何一个人的 [Friend] 实例都是单一的.
|
||||
* [Friend] 无法通过任何方式直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
|
||||
*
|
||||
* @see FriendMessageEvent
|
||||
*/
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
abstract class Friend : QQ(), CoroutineScope {
|
||||
abstract class Friend : User(), CoroutineScope {
|
||||
/**
|
||||
* 请求头像下载链接
|
||||
*/
|
||||
|
@ -47,7 +47,6 @@ abstract class Group : Contact(), CoroutineScope {
|
||||
/**
|
||||
* 群设置
|
||||
*/
|
||||
@SinceMirai("0.30.0")
|
||||
abstract val settings: GroupSettings
|
||||
|
||||
/**
|
||||
@ -80,7 +79,6 @@ abstract class Group : Contact(), CoroutineScope {
|
||||
* 机器人在这个群里的权限
|
||||
*
|
||||
* @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限
|
||||
* @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator]
|
||||
*
|
||||
* @see BotGroupPermissionChangeEvent 机器人群员修改
|
||||
*/
|
||||
@ -123,7 +121,6 @@ abstract class Group : Contact(), CoroutineScope {
|
||||
* @return 退出成功时 true; 已经退出时 false
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.37.0")
|
||||
abstract suspend fun quit(): Boolean
|
||||
|
||||
/**
|
||||
@ -197,7 +194,6 @@ abstract class Group : Contact(), CoroutineScope {
|
||||
@Suppress("FunctionName")
|
||||
@JvmName("quit")
|
||||
@JavaFriendlyAPI
|
||||
@SinceMirai("0.39.4")
|
||||
fun __quitBlockingForJava__(): Boolean = runBlocking { quit() }
|
||||
}
|
||||
|
||||
@ -206,7 +202,6 @@ abstract class Group : Contact(), CoroutineScope {
|
||||
*
|
||||
* @see Group.settings 获取群设置
|
||||
*/
|
||||
@SinceMirai("0.30.0")
|
||||
interface GroupSettings {
|
||||
/**
|
||||
* 入群公告, 没有时为空字符串.
|
||||
|
@ -27,4 +27,4 @@ expect abstract class ContactJavaFriendlyAPI internal constructor()
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@MiraiInternalAPI
|
||||
@JavaFriendlyAPI
|
||||
expect abstract class MemberJavaFriendlyAPI internal constructor() : QQ // 将来会改为 User
|
||||
expect abstract class MemberJavaFriendlyAPI internal constructor() : User
|
@ -19,19 +19,19 @@ import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.toMessage
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
/**
|
||||
* 群成员.
|
||||
* 代表一位群成员.
|
||||
*
|
||||
* 群成员可能也是好友, 但他们在对象类型上不同.
|
||||
* 群成员可以通过 [asFriend] 得到相关好友对象.
|
||||
*
|
||||
* ## 与好友相关的操作
|
||||
* [Member.isFriend] 判断此成员是否为好友
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@OptIn(MiraiInternalAPI::class, JavaFriendlyAPI::class)
|
||||
@ -95,12 +95,16 @@ abstract class Member : MemberJavaFriendlyAPI() {
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Member.isMuted 判断此成员是否正处于禁言状态中
|
||||
* @see unmute 取消禁言此成员
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*
|
||||
* @throws PermissionDeniedException 无权限修改时抛出
|
||||
*/
|
||||
@JvmSynthetic
|
||||
abstract suspend fun mute(durationSeconds: Int)
|
||||
@ -110,8 +114,11 @@ abstract class Member : MemberJavaFriendlyAPI() {
|
||||
*
|
||||
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
* @see Member.isMuted 判断此成员是否正处于禁言状态中
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件
|
||||
*
|
||||
* @throws PermissionDeniedException 无权限修改时抛出
|
||||
*/
|
||||
@JvmSynthetic
|
||||
abstract suspend fun unmute()
|
||||
@ -164,26 +171,22 @@ abstract class Member : MemberJavaFriendlyAPI() {
|
||||
*
|
||||
* @throws IllegalStateException 当此成员不是好友时抛出
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
fun Member.asFriend(): Friend = this.bot.getFriendOrNull(this.id) ?: error("$this is not a friend")
|
||||
|
||||
/**
|
||||
* 得到此成员作为好友的对象.
|
||||
* 得到此成员作为好友的对象, 当此成员不是好友时返回 `null`
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
fun Member.asFriendOrNull(): Friend? = this.bot.getFriendOrNull(this.id)
|
||||
|
||||
/**
|
||||
* 判断此成员是否为好友
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
inline val Member.isFriend: Boolean
|
||||
get() = this.bot.friends.contains(this.id)
|
||||
|
||||
/**
|
||||
* 如果此成员是好友, 则执行 [block] 并返回其返回值. 否则返回 `null`
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? {
|
||||
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
|
||||
|
||||
@ -202,27 +205,15 @@ val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty()
|
||||
*
|
||||
* 否则返回 [Member.nick]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
val User.nameCardOrNick: String
|
||||
get() = when (this) {
|
||||
is Member -> this.nameCardOrNick
|
||||
else -> this.nick
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断改成员是否处于禁言状态.
|
||||
*/
|
||||
@JvmName("isMuted2") // make compiler happy
|
||||
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
@kotlin.internal.InlineOnly // val Member.isMuted 编译在 JVM 也会产生 `public boolean isMuted(Member receive)`
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("use property instead", ReplaceWith("this.isMuted"))
|
||||
inline fun Member.isMuted(): Boolean = this.isMuted
|
||||
|
||||
/**
|
||||
* 判断群成员是否处于禁言状态.
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
val Member.isMuted: Boolean
|
||||
get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt()
|
||||
|
||||
|
@ -7,17 +7,24 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("NOTHING_TO_INLINE")
|
||||
@file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.internal.InlineOnly
|
||||
|
||||
/**
|
||||
* 群成员的权限.
|
||||
*
|
||||
* 可通过 [compareTo] 判断是否有更高的权限.
|
||||
*
|
||||
* @see isOwner 判断权限是否为群主
|
||||
* @see isOperator 判断权限是否为管理员或群主
|
||||
*
|
||||
* @see Member.isOwner 对 [Member] 的扩展函数, 判断此成员是否为群主
|
||||
* @see Member.isOperator 对 [Member] 的扩展函数, 判断此成员是否为管理员或群主
|
||||
* @see Member.isAdministrator 对 [Member] 的扩展函数, 判断此成员是否为管理员
|
||||
*/
|
||||
enum class MemberPermission : Comparable<MemberPermission> {
|
||||
/**
|
||||
@ -38,7 +45,6 @@ enum class MemberPermission : Comparable<MemberPermission> {
|
||||
/**
|
||||
* 权限等级. [OWNER] 为 2, [ADMINISTRATOR] 为 1, [MEMBER] 为 0
|
||||
*/
|
||||
@SinceMirai("0.32.0")
|
||||
val level: Int
|
||||
get() = ordinal
|
||||
}
|
||||
@ -46,16 +52,19 @@ enum class MemberPermission : Comparable<MemberPermission> {
|
||||
/**
|
||||
* 判断权限是否为群主
|
||||
*/
|
||||
@InlineOnly
|
||||
inline fun MemberPermission.isOwner(): Boolean = this == MemberPermission.OWNER
|
||||
|
||||
/**
|
||||
* 判断权限是否为管理员
|
||||
*/
|
||||
@InlineOnly
|
||||
inline fun MemberPermission.isAdministrator(): Boolean = this == MemberPermission.ADMINISTRATOR
|
||||
|
||||
/**
|
||||
* 判断权限是否为管理员或群主
|
||||
*/
|
||||
@InlineOnly
|
||||
inline fun MemberPermission.isOperator(): Boolean = isAdministrator() || isOwner()
|
||||
|
||||
|
||||
@ -85,7 +94,7 @@ class PermissionDeniedException : IllegalStateException {
|
||||
}
|
||||
|
||||
/**
|
||||
* 要求 [Bot] 在这个群里的权限为 [required], 否则抛出异常 [PermissionDeniedException]
|
||||
* 要求 [Bot] 在这个群里的权限至少为 [required], 否则抛出异常 [PermissionDeniedException]
|
||||
*
|
||||
* @throws PermissionDeniedException
|
||||
*/
|
||||
@ -95,7 +104,7 @@ inline fun Group.checkBotPermission(
|
||||
"Permission denied: required $required, got actual $botPermission for $bot in group $id"
|
||||
}
|
||||
) {
|
||||
if (botPermission != required) {
|
||||
if (botPermission < required) {
|
||||
throw PermissionDeniedException(lazyMessage())
|
||||
}
|
||||
}
|
||||
@ -105,12 +114,9 @@ inline fun Group.checkBotPermission(
|
||||
*
|
||||
* @throws PermissionDeniedException
|
||||
*/
|
||||
@Deprecated("use checkBotPermission", ReplaceWith("checkBotPermission(MemberPermission.ADMINISTRATOR)"))
|
||||
inline fun Group.checkBotPermissionOperator(
|
||||
crossinline lazyMessage: () -> String = {
|
||||
"Permission denied: required ${MemberPermission.ADMINISTRATOR} or ${MemberPermission.OWNER}, got actual $botPermission for $bot in group $id"
|
||||
}
|
||||
) {
|
||||
if (!botPermission.isOperator()) {
|
||||
throw PermissionDeniedException(lazyMessage())
|
||||
}
|
||||
}
|
||||
) = checkBotPermission(MemberPermission.ADMINISTRATOR, lazyMessage)
|
@ -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>
|
||||
}
|
@ -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)"
|
||||
}
|
||||
}
|
@ -4,14 +4,12 @@ package net.mamoe.mirai.data
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
|
||||
|
||||
/**
|
||||
* 群统计信息
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.28.0")
|
||||
@Serializable
|
||||
data class GroupActiveData(
|
||||
|
||||
|
@ -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 = ", ")
|
||||
}
|
@ -11,9 +11,7 @@
|
||||
|
||||
package net.mamoe.mirai.data
|
||||
|
||||
import io.ktor.util.date.GMTDate
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/*
|
||||
/**
|
||||
* 个人资料
|
||||
*/
|
||||
@ -61,4 +59,4 @@ enum class Gender(val value: Byte) {
|
||||
SECRET(0),
|
||||
MALE(1),
|
||||
FEMALE(2)
|
||||
}
|
||||
}*/
|
@ -70,7 +70,7 @@ interface Event {
|
||||
abstract class AbstractEvent : Event {
|
||||
@Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "PropertyName")
|
||||
@get:JvmSynthetic // so Java user won't see it
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
@Deprecated("prohibit illegal overrides", level = DeprecationLevel.HIDDEN)
|
||||
final override val DoNotImplementThisClassButExtendAbstractEvent: Nothing
|
||||
get() = throw Error("Shouldn't be reached")
|
||||
|
||||
|
@ -17,13 +17,11 @@ package net.mamoe.mirai.event
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.event.internal.*
|
||||
import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.message.TempMessage
|
||||
import net.mamoe.mirai.message.FriendMessageEvent
|
||||
import net.mamoe.mirai.message.GroupMessageEvent
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.TempMessageEvent
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmOverloads
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -33,7 +31,7 @@ import kotlin.jvm.JvmSynthetic
|
||||
* 消息事件的处理器.
|
||||
*
|
||||
* 注:
|
||||
* 接受者 T 为 [ContactMessage]
|
||||
* 接受者 T 为 [MessageEvent]
|
||||
* 参数 String 为 转为字符串了的消息 ([Message.toString])
|
||||
*/
|
||||
typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
|
||||
@ -49,7 +47,7 @@ typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
|
||||
* @see subscribeFriendMessages
|
||||
*/
|
||||
@MessageDsl
|
||||
open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
|
||||
open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, RR>(
|
||||
/**
|
||||
* 用于 [MessageListener] 无返回值的替代.
|
||||
*/
|
||||
@ -233,7 +231,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
|
||||
|
||||
/** 如果是这个人发的消息. 消息目前只会是群消息 */
|
||||
@MessageDsl
|
||||
fun sentBy(name: String): ListeningFilter = content { this is GroupMessage && this.senderName == name }
|
||||
fun sentBy(name: String): ListeningFilter = content { this is GroupMessageEvent && this.senderName == name }
|
||||
|
||||
/** 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */
|
||||
@MessageDsl
|
||||
@ -249,36 +247,37 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
|
||||
|
||||
/** 如果是好友发来的消息 */
|
||||
@MessageDsl
|
||||
fun sentByFriend(onEvent: MessageListener<FriendMessage, R>): Ret =
|
||||
content({ this is FriendMessage }) { onEvent(this as FriendMessage, it) }
|
||||
fun sentByFriend(onEvent: MessageListener<FriendMessageEvent, R>): Ret =
|
||||
content({ this is FriendMessageEvent }) { onEvent(this as FriendMessageEvent, it) }
|
||||
|
||||
/** 如果是好友发来的消息 */
|
||||
@MessageDsl
|
||||
fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage }
|
||||
fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessageEvent }
|
||||
|
||||
/** 如果是好友发来的消息 */
|
||||
/** 如果是群临时会话消息 */
|
||||
@MessageDsl
|
||||
fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessage }
|
||||
fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessageEvent }
|
||||
|
||||
/** 如果是管理员或群主发的消息 */
|
||||
@MessageDsl
|
||||
fun sentByOperator(): ListeningFilter = content { this is GroupMessage && sender.permission.isOperator() }
|
||||
fun sentByOperator(): ListeningFilter = content { this is GroupMessageEvent && sender.permission.isOperator() }
|
||||
|
||||
/** 如果是管理员发的消息 */
|
||||
@MessageDsl
|
||||
fun sentByAdministrator(): ListeningFilter = content { this is GroupMessage && sender.permission.isAdministrator() }
|
||||
fun sentByAdministrator(): ListeningFilter =
|
||||
content { this is GroupMessageEvent && sender.permission.isAdministrator() }
|
||||
|
||||
/** 如果是群主发的消息 */
|
||||
@MessageDsl
|
||||
fun sentByOwner(): ListeningFilter = content { this is GroupMessage && sender.isOwner() }
|
||||
fun sentByOwner(): ListeningFilter = content { this is GroupMessageEvent && sender.isOwner() }
|
||||
|
||||
/** 如果是来自这个群的消息 */
|
||||
@MessageDsl
|
||||
fun sentFrom(groupId: Long): ListeningFilter = content { this is GroupMessage && group.id == groupId }
|
||||
fun sentFrom(groupId: Long): ListeningFilter = content { this is GroupMessageEvent && group.id == groupId }
|
||||
|
||||
/** 如果是来自这个群的消息 */
|
||||
@MessageDsl
|
||||
fun sentFrom(group: Group): ListeningFilter = content { this is GroupMessage && group.id == group.id }
|
||||
fun sentFrom(group: Group): ListeningFilter = content { this is GroupMessageEvent && group.id == group.id }
|
||||
|
||||
/** [消息内容][Message.contentToString]包含目标为 [Bot] 的 [At] */
|
||||
@MessageDsl
|
||||
@ -440,79 +439,6 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
|
||||
}
|
||||
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) }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,9 @@ import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.qqandroid.network.Packet
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.internal.runBlocking
|
||||
import kotlin.jvm.JvmField
|
||||
import kotlin.jvm.JvmName
|
||||
@ -75,7 +77,6 @@ sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
|
||||
/**
|
||||
* 服务器主动要求更换另一个服务器
|
||||
*/
|
||||
@SinceMirai("0.37.1")
|
||||
data class RequireReconnect(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
|
||||
}
|
||||
|
||||
@ -131,7 +132,6 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
|
||||
* 消息内部 id.
|
||||
* @see MessageSource.id
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
abstract val messageInternalId: Int
|
||||
|
||||
/**
|
||||
@ -148,7 +148,7 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
|
||||
override val messageInternalId: Int,
|
||||
override val messageTime: Int,
|
||||
/**
|
||||
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id]
|
||||
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [User.id]
|
||||
*/
|
||||
val operator: Long
|
||||
) : MessageRecallEvent(), Packet {
|
||||
@ -235,20 +235,17 @@ sealed class ImageUploadEvent : BotEvent, BotActiveEvent, AbstractEvent() {
|
||||
/**
|
||||
* 机器人被踢出群或在其他客户端主动退出一个群. 在事件广播前 [Bot.groups] 就已删除这个群.
|
||||
*/
|
||||
@SinceMirai("0.36.0")
|
||||
sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent() {
|
||||
abstract val group: Group
|
||||
|
||||
/**
|
||||
* 机器人主动退出一个群.
|
||||
*/
|
||||
@SinceMirai("0.37.0")
|
||||
data class Active(override val group: Group) : BotLeaveEvent()
|
||||
|
||||
/**
|
||||
* 机器人被管理员或群主踢出群. 暂不支持获取操作人
|
||||
*/
|
||||
@SinceMirai("0.37.0")
|
||||
data class Kick(override val group: Group) : BotLeaveEvent()
|
||||
|
||||
override val bot: Bot get() = group.bot
|
||||
@ -321,7 +318,6 @@ data class GroupNameChangeEvent(
|
||||
/**
|
||||
* 操作人. 为 null 时则是机器人操作
|
||||
*/
|
||||
@SinceMirai("0.37.3")
|
||||
override val operator: Member?
|
||||
) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent() {
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@ -409,13 +405,11 @@ sealed class MemberJoinEvent(override val member: Member) : GroupMemberEvent, Bo
|
||||
/**
|
||||
* 被邀请加入群
|
||||
*/
|
||||
@SinceMirai("0.36.0")
|
||||
data class Invite(override val member: Member) : MemberJoinEvent(member)
|
||||
|
||||
/**
|
||||
* 成员主动加入群
|
||||
*/
|
||||
@SinceMirai("0.36.0")
|
||||
data class Active(override val member: Member) : MemberJoinEvent(member)
|
||||
}
|
||||
|
||||
@ -451,7 +445,6 @@ sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() {
|
||||
/**
|
||||
* [Bot] 被邀请加入一个群.
|
||||
*/
|
||||
@SinceMirai("0.39.4")
|
||||
data class BotInvitedJoinGroupRequestEvent(
|
||||
override val bot: Bot,
|
||||
/**
|
||||
@ -494,7 +487,6 @@ data class BotInvitedJoinGroupRequestEvent(
|
||||
/**
|
||||
* 一个账号请求加入群事件, [Bot] 在此群中是管理员或群主.
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
data class MemberJoinRequestEvent(
|
||||
override val bot: Bot,
|
||||
/**
|
||||
@ -566,17 +558,8 @@ data class MemberCardChangeEvent(
|
||||
*/
|
||||
val new: String,
|
||||
|
||||
override val member: Member,
|
||||
|
||||
/**
|
||||
* 此事件无法确定操作人, 将在未来版本删除
|
||||
*/
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("operator is always unknown", level = DeprecationLevel.ERROR)
|
||||
override val operator: Member?
|
||||
) : GroupMemberEvent, Packet, AbstractEvent(),
|
||||
@Deprecated("operator is always unknown", level = DeprecationLevel.ERROR)
|
||||
GroupOperableEvent
|
||||
override val member: Member
|
||||
) : GroupMemberEvent, Packet, AbstractEvent()
|
||||
|
||||
/**
|
||||
* 成员群头衔改动. 一定为群主操作
|
||||
@ -623,6 +606,8 @@ data class MemberPermissionChangeEvent(
|
||||
|
||||
/**
|
||||
* 群成员被禁言事件. 被禁言的成员都不可能是机器人本人
|
||||
*
|
||||
* @see BotMuteEvent 机器人被禁言的事件
|
||||
*/
|
||||
data class MemberMuteEvent(
|
||||
override val member: Member,
|
||||
@ -635,6 +620,8 @@ data class MemberMuteEvent(
|
||||
|
||||
/**
|
||||
* 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人
|
||||
*
|
||||
* @see BotUnmuteEvent 机器人被取消禁言的事件
|
||||
*/
|
||||
data class MemberUnmuteEvent(
|
||||
override val member: Member,
|
||||
@ -655,65 +642,36 @@ data class MemberUnmuteEvent(
|
||||
/**
|
||||
* 好友昵称改变事件. 目前仅支持解析 (来自 PC 端的修改).
|
||||
*/
|
||||
@SinceMirai("0.36.0")
|
||||
data class FriendRemarkChangeEvent(
|
||||
override val bot: Bot,
|
||||
val friend: Friend,
|
||||
override val friend: Friend,
|
||||
val newName: String
|
||||
) : BotEvent, Packet, AbstractEvent() {
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
@get:JvmSynthetic
|
||||
@get:JvmName("getFriend")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
|
||||
val friendDeprecated: QQ
|
||||
get() = friend
|
||||
}
|
||||
) : FriendEvent, Packet, AbstractEvent()
|
||||
|
||||
/**
|
||||
* 成功添加了一个新好友的事件
|
||||
*/
|
||||
@SinceMirai("0.36.0")
|
||||
data class FriendAddEvent(
|
||||
/**
|
||||
* 新好友. 已经添加到 [Bot.friends]
|
||||
*/
|
||||
val friend: Friend
|
||||
) : BotEvent, Packet, AbstractEvent() {
|
||||
override val friend: Friend
|
||||
) : FriendEvent, Packet, AbstractEvent() {
|
||||
override val bot: Bot get() = friend.bot
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
@get:JvmSynthetic
|
||||
@get:JvmName("getFriend")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
|
||||
val friendDeprecated: QQ
|
||||
get() = friend
|
||||
}
|
||||
|
||||
/**
|
||||
* 好友已被删除的事件.
|
||||
*/
|
||||
@SinceMirai("0.36.0")
|
||||
data class FriendDeleteEvent(
|
||||
val friend: Friend
|
||||
) : BotEvent, Packet, AbstractEvent() {
|
||||
override val friend: Friend
|
||||
) : FriendEvent, Packet, AbstractEvent() {
|
||||
override val bot: Bot get() = friend.bot
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
@get:JvmSynthetic
|
||||
@get:JvmName("getFriend")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
|
||||
val friendDeprecated: QQ
|
||||
get() = friend
|
||||
}
|
||||
|
||||
/**
|
||||
* 一个账号请求添加机器人为好友的事件
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
data class NewFriendRequestEvent(
|
||||
override val bot: Bot,
|
||||
/**
|
||||
@ -725,7 +683,7 @@ data class NewFriendRequestEvent(
|
||||
*/
|
||||
val message: String,
|
||||
/**
|
||||
* 请求人 [QQ.id]
|
||||
* 请求人 [User.id]
|
||||
*/
|
||||
val fromId: Long,
|
||||
/**
|
||||
|
@ -15,8 +15,6 @@ import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
* 有关一个 [Bot] 的事件
|
||||
@ -90,11 +88,4 @@ interface FriendEvent : BotEvent {
|
||||
val friend: Friend
|
||||
override val bot: Bot
|
||||
get() = friend.bot
|
||||
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
@get:JvmSynthetic
|
||||
@get:JvmName("getFriend")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
|
||||
val friendDeprecated: net.mamoe.mirai.contact.QQ
|
||||
get() = friend
|
||||
}
|
@ -12,7 +12,7 @@ package net.mamoe.mirai.event.internal
|
||||
import net.mamoe.mirai.event.MessageDsl
|
||||
import net.mamoe.mirai.event.MessageListener
|
||||
import net.mamoe.mirai.event.MessageSubscribersBuilder
|
||||
import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
|
||||
|
||||
/*
|
||||
@ -20,13 +20,13 @@ import net.mamoe.mirai.message.ContactMessage
|
||||
*/
|
||||
|
||||
@MessageDsl
|
||||
internal fun <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,
|
||||
onEvent: MessageListener<M, RR>
|
||||
): Ret =
|
||||
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,
|
||||
removeSuffix: 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,
|
||||
removePrefix: 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>,
|
||||
ignoreCase: Boolean = false,
|
||||
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) } }
|
||||
}
|
||||
|
||||
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,
|
||||
ignoreCase: Boolean = false,
|
||||
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) } }
|
||||
} 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,
|
||||
ignoreCase: Boolean = false,
|
||||
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,
|
||||
ignoreCase: Boolean = false,
|
||||
trim: Boolean = true,
|
||||
|
@ -12,7 +12,6 @@
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -25,12 +24,13 @@ import kotlin.reflect.KClass
|
||||
* @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值
|
||||
*
|
||||
* @see asyncFromEvent 本函数的异步版本
|
||||
* @see subscribe 普通地监听一个事件
|
||||
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
|
||||
*
|
||||
* @throws TimeoutCancellationException 在超时后抛出.
|
||||
* @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.39.0")
|
||||
suspend inline fun <reified E : Event, R : Any> syncFromEvent(
|
||||
timeoutMillis: Long = -1,
|
||||
crossinline mapper: suspend E.(E) -> R?
|
||||
@ -57,10 +57,12 @@ suspend inline fun <reified E : Event, R : Any> syncFromEvent(
|
||||
* @return 超时返回 `null`, 否则返回 [mapper] 返回的第一个非 `null` 值.
|
||||
*
|
||||
* @see asyncFromEvent 本函数的异步版本
|
||||
* @see subscribe 普通地监听一个事件
|
||||
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
|
||||
*
|
||||
* @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.39.0")
|
||||
suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull(
|
||||
timeoutMillis: Long,
|
||||
crossinline mapper: suspend E.(E) -> R?
|
||||
@ -80,10 +82,14 @@ suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull(
|
||||
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
|
||||
* @param coroutineContext 额外的 [CoroutineContext]
|
||||
* @param mapper 过滤转换器. 返回非 `null` 则代表得到了需要的值. [syncFromEvent] 会返回这个值
|
||||
*
|
||||
* @see syncFromEvent
|
||||
* @see asyncFromEvent
|
||||
* @see subscribe 普通地监听一个事件
|
||||
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@Suppress("DeferredIsResult")
|
||||
@SinceMirai("0.39.0")
|
||||
inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull(
|
||||
timeoutMillis: Long,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
@ -104,10 +110,14 @@ inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull(
|
||||
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
|
||||
* @param coroutineContext 额外的 [CoroutineContext]
|
||||
* @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值
|
||||
*
|
||||
* @see syncFromEvent
|
||||
* @see asyncFromEventOrNull
|
||||
* @see subscribe 普通地监听一个事件
|
||||
* @see nextEvent 挂起当前协程, 并获取下一个事件实例
|
||||
*/
|
||||
@JvmSynthetic
|
||||
@Suppress("DeferredIsResult")
|
||||
@SinceMirai("0.39.0")
|
||||
inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEvent(
|
||||
timeoutMillis: Long = -1,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
@ -132,9 +142,12 @@ internal suspend inline fun <E : Event, R> syncFromEventImpl(
|
||||
crossinline mapper: suspend E.(E) -> R?
|
||||
): R = suspendCancellableCoroutine { cont ->
|
||||
coroutineScope.subscribe(eventClass) {
|
||||
try {
|
||||
cont.resumeWith(kotlin.runCatching {
|
||||
mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
return@subscribe ListeningStatus.STOPPED
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -12,13 +12,12 @@
|
||||
package net.mamoe.mirai.event
|
||||
|
||||
import kotlinx.coroutines.*
|
||||
import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.PlainText
|
||||
import net.mamoe.mirai.message.isContextIdenticalWith
|
||||
import net.mamoe.mirai.message.nextMessage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.experimental.ExperimentalTypeInference
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -59,9 +58,8 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @see subscribeMessages
|
||||
* @see nextMessage 挂起协程并等待下一条消息
|
||||
*/
|
||||
@SinceMirai("0.29.0")
|
||||
@Suppress("unused")
|
||||
suspend inline fun <reified T : ContactMessage> T.whileSelectMessages(
|
||||
suspend inline fun <reified T : MessageEvent> T.whileSelectMessages(
|
||||
timeoutMillis: Long = -1,
|
||||
filterContext: Boolean = true,
|
||||
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
|
||||
@ -72,9 +70,8 @@ suspend inline fun <reified T : ContactMessage> T.whileSelectMessages(
|
||||
*/
|
||||
@OptIn(ExperimentalTypeInference::class)
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.29.0")
|
||||
@JvmName("selectMessages1")
|
||||
suspend inline fun <reified T : ContactMessage> T.selectMessagesUnit(
|
||||
suspend inline fun <reified T : MessageEvent> T.selectMessagesUnit(
|
||||
timeoutMillis: Long = -1,
|
||||
filterContext: Boolean = true,
|
||||
crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, Unit>.() -> Unit
|
||||
@ -101,10 +98,9 @@ suspend inline fun <reified T : ContactMessage> T.selectMessagesUnit(
|
||||
*
|
||||
* @see nextMessage 挂起协程并等待下一条消息
|
||||
*/
|
||||
@SinceMirai("0.29.0")
|
||||
@Suppress("unused") // false positive
|
||||
// @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,
|
||||
filterContext: Boolean = true,
|
||||
// @BuilderInference
|
||||
@ -120,8 +116,7 @@ suspend inline fun <reified T : ContactMessage, R> T.selectMessages(
|
||||
* @see MessageSelectBuilderUnit 查看上层 API
|
||||
*/
|
||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||
@SinceMirai("0.29.0")
|
||||
abstract class MessageSelectBuilder<M : ContactMessage, R> @PublishedApi internal constructor(
|
||||
abstract class MessageSelectBuilder<M : MessageEvent, R> @PublishedApi internal constructor(
|
||||
ownerMessagePacket: M,
|
||||
stub: Any?,
|
||||
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)
|
||||
override fun Regex.findingReply(replier: suspend M.(MatchResult) -> Any?) = error("prohibited")
|
||||
|
||||
@Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
|
||||
override fun String.startsWithReply(replier: suspend M.(String) -> Any?) = error("prohibited")
|
||||
|
||||
@Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN)
|
||||
override fun String.endsWithReply(replier: suspend M.(String) -> Any?) = error("prohibited")
|
||||
|
||||
@ -238,8 +230,7 @@ abstract class MessageSelectBuilder<M : ContactMessage, R> @PublishedApi interna
|
||||
*
|
||||
* @see MessageSubscribersBuilder 查看上层 API
|
||||
*/
|
||||
@SinceMirai("0.29.0")
|
||||
abstract class MessageSelectBuilderUnit<M : ContactMessage, R> @PublishedApi internal constructor(
|
||||
abstract class MessageSelectBuilderUnit<M : MessageEvent, R> @PublishedApi internal constructor(
|
||||
private val ownerMessagePacket: M,
|
||||
stub: Any?,
|
||||
subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit
|
||||
@ -455,7 +446,7 @@ class MessageSelectionTimeoutException : RuntimeException()
|
||||
|
||||
@JvmSynthetic
|
||||
@PublishedApi
|
||||
internal suspend inline fun <R> withTimeoutOrCoroutineScope(
|
||||
internal suspend inline fun <R> withSilentTimeoutOrCoroutineScope(
|
||||
timeoutMillis: Long,
|
||||
noinline block: suspend CoroutineScope.() -> R
|
||||
): R {
|
||||
@ -483,13 +474,13 @@ internal val ExceptionHandlerIgnoringCancellationException = CoroutineExceptionH
|
||||
@PublishedApi
|
||||
@BuilderInference
|
||||
@OptIn(ExperimentalTypeInference::class)
|
||||
internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl(
|
||||
internal suspend inline fun <reified T : MessageEvent, R> T.selectMessagesImpl(
|
||||
timeoutMillis: Long = -1,
|
||||
isUnit: Boolean,
|
||||
filterContext: Boolean = true,
|
||||
@BuilderInference
|
||||
crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, R>.() -> Unit
|
||||
): R = withTimeoutOrCoroutineScope(timeoutMillis) {
|
||||
): R = withSilentTimeoutOrCoroutineScope(timeoutMillis) {
|
||||
var deferred: CompletableDeferred<R>? = CompletableDeferred()
|
||||
coroutineContext[Job]!!.invokeOnCompletion {
|
||||
deferred?.cancel()
|
||||
@ -509,7 +500,7 @@ internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl
|
||||
SELECT_MESSAGE_STUB,
|
||||
outside
|
||||
) {
|
||||
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope
|
||||
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
|
||||
override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred
|
||||
override fun default(onEvent: MessageListener<T, R>) {
|
||||
defaultListeners += onEvent
|
||||
@ -525,7 +516,7 @@ internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl
|
||||
SELECT_MESSAGE_STUB,
|
||||
outside
|
||||
) {
|
||||
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope
|
||||
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
|
||||
override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred
|
||||
override fun default(onEvent: MessageListener<T, R>) {
|
||||
defaultListeners += onEvent
|
||||
@ -582,11 +573,11 @@ internal suspend inline fun <reified T : ContactMessage, R> T.selectMessagesImpl
|
||||
|
||||
@Suppress("unused")
|
||||
@PublishedApi
|
||||
internal suspend inline fun <reified T : ContactMessage> T.whileSelectMessagesImpl(
|
||||
internal suspend inline fun <reified T : MessageEvent> T.whileSelectMessagesImpl(
|
||||
timeoutMillis: Long = -1,
|
||||
filterContext: Boolean = true,
|
||||
crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit
|
||||
) = withTimeoutOrCoroutineScope(timeoutMillis) {
|
||||
) = withSilentTimeoutOrCoroutineScope(timeoutMillis) {
|
||||
var deferred: CompletableDeferred<Boolean>? = CompletableDeferred()
|
||||
coroutineContext[Job]!!.invokeOnCompletion {
|
||||
deferred?.cancel()
|
||||
@ -605,7 +596,7 @@ internal suspend inline fun <reified T : ContactMessage> T.whileSelectMessagesIm
|
||||
SELECT_MESSAGE_STUB,
|
||||
outside
|
||||
) {
|
||||
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withTimeoutOrCoroutineScope
|
||||
override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope
|
||||
override fun obtainCurrentDeferred(): CompletableDeferred<Boolean>? = deferred
|
||||
override fun default(onEvent: MessageListener<T, Boolean>) {
|
||||
defaultListeners += onEvent
|
||||
|
@ -17,18 +17,17 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.ReceiveChannel
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.event.events.BotEvent
|
||||
import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.message.FriendMessage
|
||||
import net.mamoe.mirai.message.GroupMessage
|
||||
import net.mamoe.mirai.message.TempMessage
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.message.FriendMessageEvent
|
||||
import net.mamoe.mirai.message.GroupMessageEvent
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.TempMessageEvent
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
|
||||
typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder<ContactMessage, Listener<ContactMessage>, Unit, Unit>
|
||||
typealias MessagePacketSubscribersBuilder = MessageSubscribersBuilder<MessageEvent, Listener<MessageEvent>, Unit, Unit>
|
||||
|
||||
/**
|
||||
* 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话.
|
||||
@ -47,7 +46,7 @@ fun <R> CoroutineScope.subscribeMessages(
|
||||
}
|
||||
|
||||
return MessagePacketSubscribersBuilder(Unit)
|
||||
{ filter, messageListener: MessageListener<ContactMessage, Unit> ->
|
||||
{ filter, messageListener: MessageListener<MessageEvent, Unit> ->
|
||||
// subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [messageListener]
|
||||
// messageListener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块.
|
||||
subscribeAlways(coroutineContext, concurrencyKind) {
|
||||
@ -59,7 +58,7 @@ fun <R> CoroutineScope.subscribeMessages(
|
||||
}.run(listeners)
|
||||
}
|
||||
|
||||
typealias GroupMessageSubscribersBuilder = MessageSubscribersBuilder<GroupMessage, Listener<GroupMessage>, Unit, Unit>
|
||||
typealias GroupMessageSubscribersBuilder = MessageSubscribersBuilder<GroupMessageEvent, Listener<GroupMessageEvent>, Unit, Unit>
|
||||
|
||||
/**
|
||||
* 订阅来自所有 [Bot] 的所有群消息事件
|
||||
@ -84,7 +83,7 @@ fun <R> CoroutineScope.subscribeGroupMessages(
|
||||
}.run(listeners)
|
||||
}
|
||||
|
||||
typealias FriendMessageSubscribersBuilder = MessageSubscribersBuilder<FriendMessage, Listener<FriendMessage>, Unit, Unit>
|
||||
typealias FriendMessageSubscribersBuilder = MessageSubscribersBuilder<FriendMessageEvent, Listener<FriendMessageEvent>, Unit, Unit>
|
||||
|
||||
/**
|
||||
* 订阅来自所有 [Bot] 的所有好友消息事件
|
||||
@ -109,7 +108,7 @@ fun <R> CoroutineScope.subscribeFriendMessages(
|
||||
}.run(listeners)
|
||||
}
|
||||
|
||||
typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder<TempMessage, Listener<TempMessage>, Unit, Unit>
|
||||
typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder<TempMessageEvent, Listener<TempMessageEvent>, Unit, Unit>
|
||||
|
||||
/**
|
||||
* 订阅来自所有 [Bot] 的所有临时会话消息事件
|
||||
@ -211,7 +210,6 @@ fun <R> Bot.subscribeFriendMessages(
|
||||
*
|
||||
* @see CoroutineScope.incoming 打开一个指定事件的接收通道
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun <R> Bot.subscribeTempMessages(
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
|
@ -206,7 +206,6 @@ inline fun <reified E : Event> CoroutineScope.subscribe(
|
||||
*
|
||||
* @see CoroutineScope.subscribe
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun <E : Event> CoroutineScope.subscribe(
|
||||
eventClass: KClass<E>,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
@ -238,7 +237,6 @@ inline fun <reified E : Event> CoroutineScope.subscribeAlways(
|
||||
/**
|
||||
* @see CoroutineScope.subscribeAlways
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun <E : Event> CoroutineScope.subscribeAlways(
|
||||
eventClass: KClass<E>,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
@ -271,7 +269,6 @@ inline fun <reified E : Event> CoroutineScope.subscribeOnce(
|
||||
/**
|
||||
* @see CoroutineScope.subscribeOnce
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun <E : Event> CoroutineScope.subscribeOnce(
|
||||
eventClass: KClass<E>,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
@ -308,7 +305,6 @@ inline fun <reified E : BotEvent> Bot.subscribe(
|
||||
*
|
||||
* @see Bot.subscribe
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun <E : BotEvent> Bot.subscribe(
|
||||
eventClass: KClass<E>,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
@ -345,7 +341,6 @@ inline fun <reified E : BotEvent> Bot.subscribeAlways(
|
||||
*
|
||||
* @see Bot.subscribeAlways
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun <E : BotEvent> Bot.subscribeAlways(
|
||||
eventClass: KClass<E>,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
@ -376,7 +371,6 @@ inline fun <reified E : BotEvent> Bot.subscribeOnce(
|
||||
*
|
||||
* @see Bot.subscribeOnce
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun <E : BotEvent> Bot.subscribeOnce(
|
||||
eventClass: KClass<E>,
|
||||
coroutineContext: CoroutineContext = EmptyCoroutineContext,
|
||||
|
@ -14,7 +14,6 @@ import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.data.*
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.WeakRef
|
||||
|
||||
/**
|
||||
@ -73,7 +72,6 @@ interface LowLevelBotAPIAccessor {
|
||||
* 获取群公告列表
|
||||
* @param page 页码
|
||||
*/
|
||||
@SinceMirai("0.28.0")
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
suspend fun _lowLevelGetAnnouncements(groupId: Long, page: Int = 1, amount: Int = 10): GroupAnnouncementList
|
||||
@ -83,7 +81,6 @@ interface LowLevelBotAPIAccessor {
|
||||
*
|
||||
* @return 公告的fid
|
||||
*/
|
||||
@SinceMirai("0.28.0")
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
suspend fun _lowLevelSendAnnouncement(groupId: Long, announcement: GroupAnnouncement): String
|
||||
@ -93,7 +90,6 @@ interface LowLevelBotAPIAccessor {
|
||||
* 删除群公告
|
||||
* @param fid [GroupAnnouncement.fid]
|
||||
*/
|
||||
@SinceMirai("0.28.0")
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
suspend fun _lowLevelDeleteAnnouncement(groupId: Long, fid: String)
|
||||
@ -102,7 +98,6 @@ interface LowLevelBotAPIAccessor {
|
||||
* 获取一条群公告
|
||||
* @param fid [GroupAnnouncement.fid]
|
||||
*/
|
||||
@SinceMirai("0.28.0")
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
suspend fun _lowLevelGetAnnouncement(groupId: Long, fid: String): GroupAnnouncement
|
||||
@ -111,7 +106,6 @@ interface LowLevelBotAPIAccessor {
|
||||
/**
|
||||
* 获取群活跃信息
|
||||
*/
|
||||
@SinceMirai("0.29.0")
|
||||
@LowLevelAPI
|
||||
@MiraiExperimentalAPI
|
||||
suspend fun _lowLevelGetGroupActiveData(groupId: Long): GroupActiveData
|
||||
|
@ -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)
|
||||
}
|
@ -13,43 +13,33 @@ package net.mamoe.mirai.message
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.QQ
|
||||
import net.mamoe.mirai.event.BroadcastControllable
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.OnlineMessageSource
|
||||
import net.mamoe.mirai.message.data.source
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
|
||||
/**
|
||||
* 好友消息
|
||||
* 机器人收到的好友消息的事件
|
||||
*
|
||||
* @see MessageEvent
|
||||
*/
|
||||
class FriendMessage constructor(
|
||||
sender: Friend,
|
||||
class FriendMessageEvent constructor(
|
||||
override val sender: Friend,
|
||||
override val message: MessageChain,
|
||||
override val time: Int
|
||||
) : ContactMessage(), BroadcastControllable {
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
constructor(sender: QQ, message: MessageChain) : this(sender as Friend, message, currentTimeSeconds.toInt())
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
constructor(sender: Friend, message: MessageChain) : this(sender, message, currentTimeSeconds.toInt())
|
||||
|
||||
) : @PlannedRemoval("1.2.0") FriendMessage(), BroadcastControllable {
|
||||
init {
|
||||
val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message")
|
||||
val source =
|
||||
message.getOrNull(MessageSource) ?: throw IllegalArgumentException("Cannot find MessageSource from message")
|
||||
check(source is OnlineMessageSource.Incoming.FromFriend) { "source provided to a FriendMessage must be an instance of OnlineMessageSource.Incoming.FromFriend" }
|
||||
}
|
||||
|
||||
override val sender: Friend by sender.unsafeWeakRef()
|
||||
override val bot: Bot get() = sender.bot
|
||||
override val subject: Friend get() = sender
|
||||
override val senderName: String get() = sender.nick
|
||||
override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend
|
||||
|
||||
override fun toString(): String = "FriendMessage(sender=${sender.id}, message=$message)"
|
||||
override fun toString(): String = "FriendMessageEvent(sender=${sender.id}, message=$message)"
|
||||
}
|
@ -7,6 +7,8 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("DEPRECATION_ERROR", "unused", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
@ -16,33 +18,28 @@ import net.mamoe.mirai.contact.MemberPermission
|
||||
import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import net.mamoe.mirai.utils.getValue
|
||||
import net.mamoe.mirai.utils.unsafeWeakRef
|
||||
|
||||
@Suppress("unused", "NOTHING_TO_INLINE")
|
||||
class GroupMessage(
|
||||
/**
|
||||
* 机器人收到的群消息的事件
|
||||
*
|
||||
* @see MessageEvent
|
||||
*/
|
||||
class GroupMessageEvent(
|
||||
override val senderName: String,
|
||||
/**
|
||||
* 发送方权限.
|
||||
*/
|
||||
val permission: MemberPermission,
|
||||
sender: Member,
|
||||
override val sender: Member,
|
||||
override val message: MessageChain,
|
||||
override val time: Int
|
||||
) : ContactMessage(), Event {
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
constructor(senderName: String, permission: MemberPermission, sender: Member, message: MessageChain) :
|
||||
this(senderName, permission, sender, message, currentTimeSeconds.toInt())
|
||||
|
||||
) : @PlannedRemoval("1.2.0") GroupMessage(), Event {
|
||||
init {
|
||||
val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message")
|
||||
check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessage must be an instance of OnlineMessageSource.Incoming.FromGroup" }
|
||||
}
|
||||
|
||||
override val sender: Member by sender.unsafeWeakRef()
|
||||
val group: Group get() = sender.group
|
||||
inline val group: Group get() = sender.group
|
||||
override val bot: Bot get() = sender.bot
|
||||
|
||||
override val subject: Group get() = group
|
||||
@ -52,5 +49,5 @@ class GroupMessage(
|
||||
inline fun At.asMember(): Member = group[this.target]
|
||||
|
||||
override fun toString(): String =
|
||||
"GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
|
||||
"GroupMessageEvent(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)"
|
||||
}
|
@ -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()
|
@ -19,7 +19,6 @@ import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.recallIn
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.internal.runBlocking
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
@ -35,7 +34,7 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @param target 消息发送对象
|
||||
*
|
||||
* @see Group.sendMessage 发送群消息, 返回回执(此对象)
|
||||
* @see QQ.sendMessage 发送群消息, 返回回执(此对象)
|
||||
* @see User.sendMessage 发送群消息, 返回回执(此对象)
|
||||
* @see Member.sendMessage 发送临时消息, 返回回执(此对象)
|
||||
*
|
||||
* @see MessageReceipt.sourceId 源 id
|
||||
@ -48,7 +47,7 @@ open class MessageReceipt<out C : Contact>(
|
||||
*/
|
||||
val source: OnlineMessageSource.Outgoing,
|
||||
/**
|
||||
* 发送目标, 为 [Group] 或 [QQ] 或 [Member]
|
||||
* 发送目标, 为 [Group] 或 [Friend] 或 [Member]
|
||||
*/
|
||||
val target: C,
|
||||
|
||||
@ -89,16 +88,6 @@ open class MessageReceipt<out C : Contact>(
|
||||
fun __quoteBlockingForJava__(): QuoteReply {
|
||||
return this.quote()
|
||||
}
|
||||
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmSynthetic
|
||||
@JavaFriendlyAPI
|
||||
@JvmName("recall")
|
||||
fun __recallInBlockingForJava__2(timeMillis: Long): Job {
|
||||
return recallIn(timeMillis = timeMillis)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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()
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
@file:Suppress("DEPRECATION_ERROR", "unused", "NOTHING_TO_INLINE")
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
@ -9,28 +11,22 @@ import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.MessageSource
|
||||
import net.mamoe.mirai.message.data.OnlineMessageSource
|
||||
import net.mamoe.mirai.message.data.source
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
||||
/**
|
||||
* 临时会话消息
|
||||
* 机器人收到的群临时会话消息的事件
|
||||
*
|
||||
* @see MessageEvent
|
||||
*/
|
||||
@SinceMirai("0.35.0")
|
||||
class TempMessage(
|
||||
sender: Member,
|
||||
class TempMessageEvent(
|
||||
override val sender: Member,
|
||||
override val message: MessageChain,
|
||||
override val time: Int
|
||||
) : ContactMessage(), BroadcastControllable {
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("", level = DeprecationLevel.HIDDEN)
|
||||
constructor(sender: Member, message: MessageChain) :
|
||||
this(sender, message, currentTimeSeconds.toInt())
|
||||
|
||||
) : TempMessage(), BroadcastControllable {
|
||||
init {
|
||||
val source = message.getOrNull(MessageSource) ?: error("Cannot find MessageSource from message")
|
||||
check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a TempMessage must be an instance of OnlineMessageSource.Incoming.FromTemp" }
|
||||
}
|
||||
|
||||
override val sender: Member by sender.unsafeWeakRef()
|
||||
override val bot: Bot get() = sender.bot
|
||||
override val subject: Member get() = sender
|
||||
inline val group: Group get() = sender.group
|
||||
@ -38,5 +34,5 @@ class TempMessage(
|
||||
override val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp
|
||||
|
||||
override fun toString(): String =
|
||||
"TempMessage(sender=${sender.id} from group(${sender.group.id}), message=$message)"
|
||||
"TempMessageEvent(sender=${sender.id} from group(${sender.group.id}), message=$message)"
|
||||
}
|
@ -17,7 +17,6 @@ package net.mamoe.mirai.message.data
|
||||
import net.mamoe.mirai.LowLevelAPI
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.contact.nameCardOrNick
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmStatic
|
||||
@ -73,15 +72,6 @@ private constructor(val target: Long, val display: String) :
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_FUNCTION_RETURN_TYPE")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmName("followedBy")
|
||||
@JvmSynthetic
|
||||
override fun followedBy1(tail: Message): CombinedMessage {
|
||||
return followedByInternalForBinaryCompatibility(tail)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,6 @@
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
@ -28,8 +27,6 @@ private const val displayA = "@全体成员"
|
||||
object AtAll :
|
||||
Message.Key<AtAll>,
|
||||
MessageContent {
|
||||
|
||||
@SinceMirai("0.31.2")
|
||||
const val display = displayA
|
||||
override val typeName: String
|
||||
get() = "AtAll"
|
||||
|
@ -33,7 +33,6 @@ import net.mamoe.mirai.utils.*
|
||||
*
|
||||
* @see CustomMessageMetadata 自定义消息元数据
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
@MiraiExperimentalAPI
|
||||
sealed class CustomMessage : SingleMessage {
|
||||
/**
|
||||
@ -181,7 +180,6 @@ sealed class CustomMessage : SingleMessage {
|
||||
* @see CustomMessage 查看更多信息
|
||||
* @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
@MiraiExperimentalAPI
|
||||
abstract class CustomMessageMetadata : CustomMessage(), MessageMetadata {
|
||||
companion object Key : Message.Key<CustomMessageMetadata> {
|
||||
|
@ -12,11 +12,13 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.User
|
||||
import net.mamoe.mirai.contact.nameCardOrNick
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.data.ForwardMessage.DisplayStrategy
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import kotlin.jvm.JvmOverloads
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -72,11 +74,10 @@ import kotlin.jvm.JvmSynthetic
|
||||
*
|
||||
* ### 构造
|
||||
* - 使用 [DSL][buildForwardMessage]
|
||||
* - 通过 [ContactMessage] 集合转换: [toForwardMessage]
|
||||
* - 通过 [MessageEvent] 集合转换: [toForwardMessage]
|
||||
*
|
||||
* @see buildForwardMessage
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
class ForwardMessage @JvmOverloads constructor(
|
||||
/**
|
||||
* 消息列表
|
||||
@ -184,9 +185,8 @@ class ForwardMessage @JvmOverloads constructor(
|
||||
/**
|
||||
* 转换为 [ForwardMessage]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmOverloads
|
||||
fun Iterable<ContactMessage>.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage {
|
||||
fun Iterable<MessageEvent>.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage {
|
||||
val iterator = this.iterator()
|
||||
if (!iterator.hasNext()) return ForwardMessage(emptyList(), displayStrategy)
|
||||
return ForwardMessage(
|
||||
@ -205,7 +205,6 @@ fun Message.toForwardMessage(
|
||||
/**
|
||||
* 转换为 [ForwardMessage]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmOverloads
|
||||
fun Message.toForwardMessage(
|
||||
senderId: Long,
|
||||
@ -220,7 +219,6 @@ fun Message.toForwardMessage(
|
||||
* @see ForwardMessageBuilder 查看 DSL 帮助
|
||||
* @see ForwardMessage 查看转发消息说明
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmSynthetic
|
||||
inline fun buildForwardMessage(
|
||||
context: Contact,
|
||||
@ -234,9 +232,8 @@ inline fun buildForwardMessage(
|
||||
* @see ForwardMessageBuilder 查看 DSL 帮助
|
||||
* @see ForwardMessage 查看转发消息说明
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmSynthetic
|
||||
inline fun ContactMessage.buildForwardMessage(
|
||||
inline fun MessageEvent.buildForwardMessage(
|
||||
context: Contact = this.subject,
|
||||
displayStrategy: DisplayStrategy = DisplayStrategy,
|
||||
block: ForwardMessageBuilder.() -> Unit
|
||||
@ -247,7 +244,6 @@ inline fun ContactMessage.buildForwardMessage(
|
||||
/**
|
||||
* 标记转发消息 DSL
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||
@DslMarker
|
||||
annotation class ForwardMessageDsl
|
||||
@ -311,7 +307,6 @@ annotation class ForwardMessageDsl
|
||||
*
|
||||
* `S named "name1" named "name2" says M` 最终的发送人名称为 `"name2"`
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
class ForwardMessageBuilder private constructor(
|
||||
/**
|
||||
* 消息语境. 可为 [Group] 或 [User]
|
||||
|
@ -17,7 +17,6 @@ import net.mamoe.mirai.message.data.PokeMessage.Types
|
||||
import net.mamoe.mirai.message.data.VipFace.Companion
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.*
|
||||
|
||||
/**
|
||||
@ -26,7 +25,6 @@ import kotlin.jvm.*
|
||||
* @see PokeMessage 戳一戳
|
||||
* @see FlashImage 闪照
|
||||
*/
|
||||
@SinceMirai("0.31.0")
|
||||
sealed class HummerMessage : MessageContent {
|
||||
companion object Key : Message.Key<HummerMessage> {
|
||||
override val typeName: String
|
||||
@ -44,7 +42,6 @@ sealed class HummerMessage : MessageContent {
|
||||
*
|
||||
* @see Types 使用伴生对象中的常量
|
||||
*/
|
||||
@SinceMirai("0.31.0")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
data class PokeMessage internal constructor(
|
||||
/**
|
||||
@ -158,7 +155,6 @@ data class PokeMessage internal constructor(
|
||||
*
|
||||
* @see Types 使用伴生对象中的常量
|
||||
*/
|
||||
@SinceMirai("0.39.5")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
data class VipFace internal constructor(
|
||||
/**
|
||||
@ -241,7 +237,6 @@ data class VipFace internal constructor(
|
||||
*
|
||||
* @see Image 查看图片相关信息
|
||||
*/
|
||||
@SinceMirai("0.33.0")
|
||||
sealed class FlashImage : MessageContent, HummerMessage() {
|
||||
companion object Key : Message.Key<FlashImage> {
|
||||
/**
|
||||
@ -289,22 +284,17 @@ sealed class FlashImage : MessageContent, HummerMessage() {
|
||||
override fun toString(): String = stringValue!!
|
||||
override fun contentToString(): String = "[闪照]"
|
||||
}
|
||||
|
||||
@SinceMirai("0.33.0")
|
||||
inline fun Image.flash(): FlashImage = FlashImage(this)
|
||||
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.33.0")
|
||||
inline fun GroupImage.flash(): GroupFlashImage = FlashImage(this) as GroupFlashImage
|
||||
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.33.0")
|
||||
inline fun FriendImage.flash(): FriendFlashImage = FlashImage(this) as FriendFlashImage
|
||||
|
||||
/**
|
||||
* @see FlashImage.invoke
|
||||
*/
|
||||
@SinceMirai("0.33.0")
|
||||
data class GroupFlashImage(override val image: GroupImage) : FlashImage() {
|
||||
companion object Key : Message.Key<GroupFlashImage> {
|
||||
override val typeName: String
|
||||
@ -315,7 +305,6 @@ data class GroupFlashImage(override val image: GroupImage) : FlashImage() {
|
||||
/**
|
||||
* @see FlashImage.invoke
|
||||
*/
|
||||
@SinceMirai("0.33.0")
|
||||
data class FriendFlashImage(override val image: FriendImage) : FlashImage() {
|
||||
companion object Key : Message.Key<FriendFlashImage> {
|
||||
override val typeName: String
|
||||
|
@ -22,7 +22,6 @@ import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
@ -45,6 +44,10 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @see Contact.sendImage 上传 [图片文件][ExternalImage] 并发送返回的 [Image] 作为一条消息
|
||||
* @see Image.sendTo 上传图片并得到 [Image] 消息
|
||||
*
|
||||
* ### 下载图片
|
||||
* @see Image.queryUrl 扩展函数. 查询图片下载链接
|
||||
* @see Bot.queryImageUrl 查询图片下载链接 (Java 使用)
|
||||
*
|
||||
* 查看平台 `actual` 定义以获取上传方式扩展.
|
||||
*
|
||||
* @see FlashImage 闪照
|
||||
@ -94,6 +97,7 @@ expect interface Image : Message, MessageContent {
|
||||
* @property imageId 形如 `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai` (后缀一定为 `".mirai"`)
|
||||
* @see Image 查看更多说明
|
||||
*/
|
||||
@PlannedRemoval("1.2.0") // make internal
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
// CustomFace
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
@ -109,7 +113,6 @@ sealed class GroupImage : AbstractImage() {
|
||||
* 在 Java 使用: `MessageUtils.calculateImageMd5(image)`
|
||||
*/
|
||||
@get:JvmName("calculateImageMd5")
|
||||
@SinceMirai("0.39.0")
|
||||
val Image.md5: ByteArray
|
||||
get() = calculateImageMd5ByImageId(imageId)
|
||||
|
||||
@ -119,6 +122,7 @@ val Image.md5: ByteArray
|
||||
*
|
||||
* [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度) 或 `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度)
|
||||
*/ // NotOnlineImage
|
||||
@PlannedRemoval("1.2.0") // make internal
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@OptIn(MiraiInternalAPI::class)
|
||||
sealed class FriendImage : AbstractImage() {
|
||||
@ -133,7 +137,6 @@ sealed class FriendImage : AbstractImage() {
|
||||
* `/f8f1ab55-bf8e-4236-b55e-955848d7069f`
|
||||
* @see FRIEND_IMAGE_ID_REGEX_2
|
||||
*/
|
||||
@SinceMirai("0.39.2")
|
||||
// Java: MessageUtils.FRIEND_IMAGE_ID_REGEX_1
|
||||
val FRIEND_IMAGE_ID_REGEX_1 = Regex("""/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}""")
|
||||
|
||||
@ -143,7 +146,6 @@ val FRIEND_IMAGE_ID_REGEX_1 = Regex("""/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a
|
||||
* `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206`
|
||||
* @see FRIEND_IMAGE_ID_REGEX_1
|
||||
*/
|
||||
@SinceMirai("0.39.2")
|
||||
// Java: MessageUtils.FRIEND_IMAGE_ID_REGEX_2
|
||||
val FRIEND_IMAGE_ID_REGEX_2 = Regex("""/[0-9]*-[0-9]*-[0-9a-fA-F]{32}""")
|
||||
|
||||
@ -153,21 +155,9 @@ val FRIEND_IMAGE_ID_REGEX_2 = Regex("""/[0-9]*-[0-9]*-[0-9a-fA-F]{32}""")
|
||||
* `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai`
|
||||
*/
|
||||
@Suppress("RegExpRedundantEscape") // This is required on Android
|
||||
@SinceMirai("0.39.2")
|
||||
// Java: MessageUtils.GROUP_IMAGE_ID_REGEX
|
||||
val GROUP_IMAGE_ID_REGEX = Regex("""\{[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}\.mirai""")
|
||||
|
||||
/**
|
||||
* 在 `0.39.0` 前的图片的正则表示
|
||||
*/
|
||||
@Suppress("RegExpRedundantEscape") // This is required on Android
|
||||
@Deprecated("Only for temporal use",
|
||||
replaceWith = ReplaceWith("GROUP_IMAGE_ID_REGEX", "net.mamoe.mirai.message.data.GROUP_IMAGE_ID_REGEX"))
|
||||
@SinceMirai("0.39.2")
|
||||
@PlannedRemoval("1.0.0")
|
||||
// Java: MessageUtils.GROUP_IMAGE_ID_REGEX_OLD
|
||||
val GROUP_IMAGE_ID_REGEX_OLD = Regex("""\{[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}\..*""")
|
||||
|
||||
/**
|
||||
* 通过 [Image.imageId] 构造一个 [Image] 以便发送.
|
||||
* 这个图片必须是服务器已经存在的图片.
|
||||
@ -185,7 +175,6 @@ fun Image(imageId: String): OfflineImage = when {
|
||||
imageId matches FRIEND_IMAGE_ID_REGEX_1 -> OfflineFriendImage(imageId)
|
||||
imageId matches FRIEND_IMAGE_ID_REGEX_2 -> OfflineFriendImage(imageId)
|
||||
imageId matches GROUP_IMAGE_ID_REGEX -> OfflineGroupImage(imageId)
|
||||
imageId matches GROUP_IMAGE_ID_REGEX_OLD -> OfflineGroupImage(imageId)
|
||||
else -> throw IllegalArgumentException("Illegal imageId: $imageId. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE")
|
||||
}
|
||||
|
||||
@ -279,7 +268,7 @@ data class OfflineGroupImage(
|
||||
) : GroupImage(), OfflineImage {
|
||||
init {
|
||||
@Suppress("DEPRECATION")
|
||||
require(imageId matches GROUP_IMAGE_ID_REGEX || imageId matches GROUP_IMAGE_ID_REGEX_OLD) {
|
||||
require(imageId matches GROUP_IMAGE_ID_REGEX) {
|
||||
"Illegal imageId. It must matches GROUP_IMAGE_ID_REGEX"
|
||||
}
|
||||
}
|
||||
@ -329,18 +318,10 @@ abstract class OnlineFriendImage : FriendImage(), OnlineImage
|
||||
// endregion
|
||||
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@JvmSynthetic
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@Suppress("FunctionName")
|
||||
@JsName("newImage")
|
||||
@JvmName("newImage")
|
||||
fun Image2(imageId: String): Image = Image(imageId)
|
||||
|
||||
|
||||
/**
|
||||
* 所有 [Image] 实现的基类.
|
||||
*/
|
||||
@PlannedRemoval("1.2.0") // make internal
|
||||
@Deprecated(
|
||||
"This is internal API. Use Image instead",
|
||||
level = DeprecationLevel.HIDDEN, // so that others can't see this class
|
||||
|
@ -14,6 +14,7 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Message.Key
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
@ -26,8 +27,6 @@ import kotlin.jvm.JvmSynthetic
|
||||
/**
|
||||
* 可发送的或从服务器接收的消息.
|
||||
*
|
||||
* 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] 等.
|
||||
*
|
||||
* [消息][Message] 分为
|
||||
* - [SingleMessage]:
|
||||
* - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource], [引用回复][QuoteReply].
|
||||
@ -63,13 +62,18 @@ import kotlin.jvm.JvmSynthetic
|
||||
*
|
||||
* 即, `appendable.append(message)` 相当于 `appendable.append(message.toString())`
|
||||
*
|
||||
* #### 发送消息
|
||||
* - 通过 [Contact] 中的成员函数: [Contact.sendMessage]
|
||||
* - 通过 [Message] 的扩展函数: [Message.sendTo]
|
||||
* - 在 [MessageEvent] 中使用 [MessageEvent.reply] 等捷径
|
||||
*
|
||||
* @see PlainText 纯文本
|
||||
* @see Image 图片
|
||||
* @see Face 原生表情
|
||||
* @see At 一个群成员的引用
|
||||
* @see AtAll 全体成员的引用
|
||||
* @see QuoteReply 一条消息的引用
|
||||
* @see RichMessage 富文本消息, 如 [Xml][XmlMessage], [小程序][LightApp], [Json][JsonMessage]
|
||||
* @see RichMessage 富文本消息, 如 [XML 和 JSON][ServiceMessage], [小程序][LightApp]
|
||||
* @see HummerMessage 一些特殊的消息, 如 [闪照][FlashImage], [戳一戳][PokeMessage]
|
||||
* @see CustomMessage 自定义消息类型
|
||||
*
|
||||
@ -95,7 +99,6 @@ interface Message { // must be interface. Don't consider any changes.
|
||||
/**
|
||||
* 此 [Key] 指代的 [Message] 类型名. 一般为 `class.simpleName`, 如 "QuoteReply", "PlainText"
|
||||
*/
|
||||
@SinceMirai("0.34.0")
|
||||
val typeName: String
|
||||
}
|
||||
|
||||
@ -118,7 +121,6 @@ interface Message { // must be interface. Don't consider any changes.
|
||||
*
|
||||
* @see plus `+` 操作符重载
|
||||
*/
|
||||
@SinceMirai("0.34.0")
|
||||
@JvmSynthetic // in java they should use `plus` instead
|
||||
fun followedBy(tail: Message): MessageChain = followedByImpl(tail)
|
||||
|
||||
@ -151,7 +153,6 @@ interface Message { // must be interface. Don't consider any changes.
|
||||
*
|
||||
* @see toString 得到包含 mirai 消息元素代码的, 易读的字符串
|
||||
*/
|
||||
@SinceMirai("0.34.0")
|
||||
fun contentToString(): String
|
||||
|
||||
|
||||
@ -164,7 +165,6 @@ interface Message { // must be interface. Don't consider any changes.
|
||||
*
|
||||
* @sample net.mamoe.mirai.message.data.ContentEqualsTest
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun contentEquals(another: Message, ignoreCase: Boolean = false): Boolean = contentEqualsImpl(another, ignoreCase)
|
||||
|
||||
/**
|
||||
@ -176,7 +176,6 @@ interface Message { // must be interface. Don't consider any changes.
|
||||
*
|
||||
* @sample net.mamoe.mirai.message.data.ContentEqualsTest
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun contentEquals(another: String, ignoreCase: Boolean = false): Boolean {
|
||||
if (!this.contentToString().equals(another, ignoreCase = ignoreCase)) return false
|
||||
return when (this) {
|
||||
@ -195,77 +194,13 @@ interface Message { // must be interface. Don't consider any changes.
|
||||
|
||||
// `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)`
|
||||
operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage())
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
// FOR BINARY COMPATIBILITY UNTIL 1.0.0
|
||||
//////////////////////////////////////
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@JvmSynthetic
|
||||
@Deprecated(
|
||||
"有歧义, 自行使用 contentToString() 比较",
|
||||
ReplaceWith("this.contentToString() == other"),
|
||||
DeprecationLevel.ERROR
|
||||
)
|
||||
infix fun eq(other: Message): Boolean = this.contentToString() == other.contentToString()
|
||||
|
||||
/**
|
||||
* 将 [contentToString] 与 [other] 比较
|
||||
* [Message.contentToString] 的捷径
|
||||
*/
|
||||
@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())
|
||||
}
|
||||
inline val Message.content: String get() = contentToString()
|
||||
|
||||
|
||||
/**
|
||||
@ -277,15 +212,12 @@ interface Message { // must be interface. Don't consider any changes.
|
||||
* - [PlainText] 长度为 0
|
||||
* - [MessageChain] 所有元素都满足 [isContentEmpty]
|
||||
*/
|
||||
@SinceMirai("0.39.3")
|
||||
fun Message.isContentEmpty(): Boolean = when (this) {
|
||||
is MessageMetadata -> true
|
||||
is PlainText -> this.content.isEmpty()
|
||||
is MessageChain -> this.all { it.isContentEmpty() }
|
||||
else -> false
|
||||
}
|
||||
|
||||
@SinceMirai("0.39.3")
|
||||
inline fun Message.isContentNotEmpty(): Boolean = !this.isContentEmpty()
|
||||
|
||||
inline fun Message.isPlain(): Boolean = this is PlainText
|
||||
@ -314,47 +246,19 @@ inline operator fun Message.times(count: Int): MessageChain = this.repeat(count)
|
||||
|
||||
@Suppress("OverridingDeprecatedMember")
|
||||
interface SingleMessage : Message {
|
||||
@PlannedRemoval("1.0.0")
|
||||
@JvmSynthetic
|
||||
@Deprecated(
|
||||
"有歧义, 自行使用 contentToString() 比较",
|
||||
ReplaceWith("this.contentToString() == other"),
|
||||
DeprecationLevel.ERROR
|
||||
)
|
||||
/* final */ override operator fun contains(sub: String): Boolean = sub in this.contentToString()
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@JvmSynthetic
|
||||
@Deprecated(
|
||||
"有歧义, 自行使用 contentToString() 比较",
|
||||
ReplaceWith("this.contentToString() == other"),
|
||||
DeprecationLevel.ERROR
|
||||
)
|
||||
/* final */ override infix fun eq(other: Message): Boolean = this.contentToString() == other.contentToString()
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@JvmSynthetic
|
||||
@Deprecated(
|
||||
"有歧义, 自行使用 contentToString() 比较",
|
||||
ReplaceWith("this.contentToString() == other"),
|
||||
DeprecationLevel.ERROR
|
||||
)
|
||||
/* final */ override infix fun eq(other: String): Boolean = this.contentToString() == other
|
||||
|
||||
|
||||
@PlannedRemoval("1.1.0")
|
||||
@PlannedRemoval("1.2.0")
|
||||
@JvmSynthetic
|
||||
@SinceMirai("1.0.0")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
fun length(): Int = this.toString().length
|
||||
|
||||
@PlannedRemoval("1.1.0")
|
||||
@PlannedRemoval("1.2.0")
|
||||
@JvmSynthetic
|
||||
@SinceMirai("1.0.0")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
fun charAt(index: Int): Char = this.toString()[index]
|
||||
|
||||
@PlannedRemoval("1.1.0")
|
||||
@PlannedRemoval("1.2.0")
|
||||
@JvmSynthetic
|
||||
@SinceMirai("1.0.0")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@ -379,7 +283,6 @@ interface MessageMetadata : SingleMessage
|
||||
*
|
||||
* 实现此接口的元素将会在连接时自动处理替换.
|
||||
*/
|
||||
@SinceMirai("0.34.0")
|
||||
interface ConstrainSingle<out M : Message> : MessageMetadata {
|
||||
val key: Key<M>
|
||||
}
|
||||
|
@ -10,13 +10,13 @@
|
||||
@file:JvmMultifileClass
|
||||
@file:JvmName("MessageUtils")
|
||||
@file:Suppress("unused", "NOTHING_TO_INLINE")
|
||||
@file:OptIn(MiraiInternalAPI::class)
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.JavaFriendlyAPI
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.js.JsName
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
@ -32,7 +32,7 @@ import kotlin.reflect.KProperty
|
||||
* @see asMessageChain 将单个 [Message] 转换为 [MessageChain]
|
||||
* @see asMessageChain 将 [Iterable] 或 [Sequence] 委托为 [MessageChain]
|
||||
*
|
||||
* @see foreachContent 遍历内容
|
||||
* @see forEachContent 遍历内容
|
||||
*
|
||||
* @see orNull 属性委托扩展
|
||||
* @see orElse 属性委托扩展
|
||||
@ -40,18 +40,9 @@ import kotlin.reflect.KProperty
|
||||
* @see flatten 扁平化
|
||||
*/
|
||||
interface MessageChain : Message, Iterable<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] 不参加计数.
|
||||
*/
|
||||
@SinceMirai("0.31.1")
|
||||
val size: Int
|
||||
|
||||
/**
|
||||
@ -60,56 +51,42 @@ interface MessageChain : Message, Iterable<SingleMessage> {
|
||||
* @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key]
|
||||
* @throws NoSuchElementException 当找不到这个类型的 [Message] 时
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "INAPPLICABLE_JVM_NAME")
|
||||
@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`
|
||||
*
|
||||
* @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key]
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "INAPPLICABLE_JVM_NAME")
|
||||
@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` 使用
|
||||
*/
|
||||
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
|
||||
@Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "FunctionName", "INAPPLICABLE_JVM_NAME")
|
||||
@JsName("forEachContent")
|
||||
@JvmName("forEachContent")
|
||||
@MiraiInternalAPI
|
||||
fun `__forEachContent for Java__`(block: (Message) -> Unit) {
|
||||
@JavaFriendlyAPI
|
||||
final fun __forEachContentForJava__(block: (Message) -> Unit) {
|
||||
this.forEachContent(block)
|
||||
}
|
||||
|
||||
/**
|
||||
* 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply].
|
||||
* 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [QuoteReply] 等
|
||||
* 仅供 `Java` 使用
|
||||
*/
|
||||
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
|
||||
@Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "FunctionName", "INAPPLICABLE_JVM_NAME")
|
||||
@JsName("forEach")
|
||||
@JvmName("forEach")
|
||||
@MiraiInternalAPI
|
||||
fun `__forEach for Java__`(block: (Message) -> Unit) {
|
||||
@JavaFriendlyAPI
|
||||
final fun __forEachForJava__(block: (Message) -> Unit) {
|
||||
this.forEach(block)
|
||||
}
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
|
||||
@JvmSynthetic
|
||||
@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME")
|
||||
@JvmName("get")
|
||||
fun <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
|
||||
@ -117,7 +94,6 @@ interface MessageChain : Message, Iterable<SingleMessage> {
|
||||
/**
|
||||
* 遍历每一个 [消息内容][MessageContent]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmSynthetic
|
||||
inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) {
|
||||
for (element in this) {
|
||||
@ -128,12 +104,6 @@ inline fun MessageChain.forEachContent(block: (MessageContent) -> Unit) {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("typo, use forEachContent",
|
||||
level = DeprecationLevel.ERROR,
|
||||
replaceWith = ReplaceWith("forEachContent(block)"))
|
||||
@JvmSynthetic
|
||||
inline fun MessageChain.foreachContent(block: (MessageContent) -> Unit) = forEachContent(block)
|
||||
|
||||
/**
|
||||
* 如果每一个 [消息内容][MessageContent] 都满足 [block], 返回 `true`
|
||||
*/
|
||||
@ -436,17 +406,3 @@ object EmptyMessageChain : MessageChain, Iterator<SingleMessage> {
|
||||
override fun hasNext(): Boolean = false
|
||||
override fun next(): SingleMessage = throw NoSuchElementException("EmptyMessageChain is empty.")
|
||||
}
|
||||
|
||||
/**
|
||||
* Null 的 [MessageChain].
|
||||
* 它不包含任何元素, 也没有创建任何 list.
|
||||
*/
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("ambiguous. use `null` or EmptyMessageChain instead", level = DeprecationLevel.ERROR)
|
||||
object NullMessageChain : MessageChain {
|
||||
override fun toString(): String = "NullMessageChain"
|
||||
override fun contentToString(): String = ""
|
||||
override val size: Int get() = 0
|
||||
override fun equals(other: Any?): Boolean = other === this
|
||||
override fun iterator(): MutableIterator<SingleMessage> = error("accessing NullMessageChain")
|
||||
}
|
||||
|
@ -14,7 +14,6 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
@ -159,7 +158,6 @@ open class MessageChainBuilder private constructor(
|
||||
/**
|
||||
* 将所有已有元素引用复制到一个新的 [MessageChainBuilder]
|
||||
*/
|
||||
@SinceMirai("0.38.0")
|
||||
fun copy(): MessageChainBuilder {
|
||||
return MessageChainBuilder(container.toMutableList())
|
||||
}
|
@ -16,10 +16,12 @@ package net.mamoe.mirai.message.data
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.message.ContactMessage
|
||||
import net.mamoe.mirai.message.MessageEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.recallIn
|
||||
import net.mamoe.mirai.utils.*
|
||||
import net.mamoe.mirai.utils.LazyProperty
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
@ -57,7 +59,6 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @see OfflineMessageSource 离线消息的 [MessageSource]
|
||||
*/
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
@SinceMirai("0.33.0")
|
||||
sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSource> {
|
||||
companion object Key : Message.Key<MessageSource> {
|
||||
override val typeName: String get() = "MessageSource"
|
||||
@ -93,7 +94,6 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
|
||||
*
|
||||
* 在事件中和在引用中无法保证同一条消息的 [internalId] 相同.
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
abstract val internalId: Int
|
||||
|
||||
/**
|
||||
@ -156,7 +156,7 @@ sealed class MessageSource : Message, MessageMetadata, ConstrainSingle<MessageSo
|
||||
* 此回执的 [消息源][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]
|
||||
*/
|
||||
@SinceMirai("0.33.0")
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
sealed class OnlineMessageSource : MessageSource() {
|
||||
companion object Key : Message.Key<OnlineMessageSource> {
|
||||
@ -279,34 +278,7 @@ sealed class OnlineMessageSource : MessageSource() {
|
||||
final override val target: Group get() = group
|
||||
inline val group: Group get() = sender.group
|
||||
}
|
||||
|
||||
|
||||
//////////////////////////////////
|
||||
//// FOR BINARY COMPATIBILITY ////
|
||||
//////////////////////////////////
|
||||
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("for binary compatibility until 1.0.0", level = DeprecationLevel.HIDDEN)
|
||||
@get:JvmName("target")
|
||||
@get:JvmSynthetic
|
||||
final override val target2: Any
|
||||
get() = target
|
||||
}
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("for binary compatibility until 1.0.0", level = DeprecationLevel.HIDDEN)
|
||||
@get:JvmName("target")
|
||||
@get:JvmSynthetic
|
||||
open val target2: Any
|
||||
get() = target
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@Deprecated("for binary compatibility until 1.0.0", level = DeprecationLevel.HIDDEN)
|
||||
@get:JvmName("sender")
|
||||
@get:JvmSynthetic
|
||||
open val sender2: Any
|
||||
get() = sender
|
||||
}
|
||||
|
||||
/**
|
||||
@ -315,7 +287,6 @@ sealed class OnlineMessageSource : MessageSource() {
|
||||
*
|
||||
* @see buildMessageSource 构建一个 [OfflineMessageSource]
|
||||
*/
|
||||
@SinceMirai("0.33.0")
|
||||
abstract class OfflineMessageSource : MessageSource() {
|
||||
companion object Key : Message.Key<OfflineMessageSource> {
|
||||
override val typeName: String
|
||||
@ -325,8 +296,6 @@ abstract class OfflineMessageSource : MessageSource() {
|
||||
enum class Kind {
|
||||
GROUP,
|
||||
FRIEND,
|
||||
|
||||
@SinceMirai("0.36.0")
|
||||
TEMP
|
||||
}
|
||||
|
||||
@ -380,7 +349,7 @@ fun MessageSource.quote(): QuoteReply {
|
||||
}
|
||||
|
||||
/**
|
||||
* 引用这条消息. 仅从服务器接收的消息 (即来自 [ContactMessage]) 才可以通过这个方式被引用.
|
||||
* 引用这条消息. 仅从服务器接收的消息 (即来自 [MessageEvent]) 才可以通过这个方式被引用.
|
||||
* @see QuoteReply
|
||||
*/
|
||||
fun MessageChain.quote(): QuoteReply {
|
||||
|
@ -19,7 +19,6 @@ import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.Member
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import net.mamoe.mirai.utils.currentTimeSeconds
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
@ -28,7 +27,6 @@ import kotlin.jvm.JvmSynthetic
|
||||
/**
|
||||
* 将在线消息源转换为离线消息源.
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmName("toOfflineMessageSource")
|
||||
fun OnlineMessageSource.toOffline(): OfflineMessageSource =
|
||||
OfflineMessageSourceByOnline(this)
|
||||
@ -44,7 +42,6 @@ fun OnlineMessageSource.toOffline(): OfflineMessageSource =
|
||||
* @see buildMessageSource 查看更多说明
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmName("copySource")
|
||||
fun MessageSource.copyAmend(
|
||||
block: MessageSourceAmender.() -> Unit
|
||||
@ -53,7 +50,6 @@ fun MessageSource.copyAmend(
|
||||
/**
|
||||
* 仅于 [copyAmend] 中修改 [MessageSource]
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
interface MessageSourceAmender {
|
||||
var kind: OfflineMessageSource.Kind
|
||||
var fromUin: Long
|
||||
@ -65,7 +61,6 @@ interface MessageSourceAmender {
|
||||
var originalMessage: MessageChain
|
||||
|
||||
/** 从另一个 [MessageSource] 中复制 [id], [internalId], [time]*/
|
||||
@SinceMirai("0.39.2")
|
||||
fun metadataFrom(another: MessageSource) {
|
||||
this.id = another.id
|
||||
this.internalId = another.internalId
|
||||
@ -107,7 +102,6 @@ interface MessageSourceAmender {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@SinceMirai("0.39.0")
|
||||
@JvmSynthetic
|
||||
@MiraiExperimentalAPI
|
||||
fun Bot.buildMessageSource(block: MessageSourceBuilder.() -> Unit): MessageSource {
|
@ -29,7 +29,7 @@ data class PlainText(
|
||||
val content: String
|
||||
) : MessageContent {
|
||||
|
||||
@PlannedRemoval("1.1.0")
|
||||
@PlannedRemoval("1.2.0")
|
||||
@Deprecated(
|
||||
"use content instead for clearer semantics",
|
||||
level = DeprecationLevel.WARNING,
|
||||
|
@ -16,7 +16,6 @@ package net.mamoe.mirai.message.data
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
@ -43,7 +42,6 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @see MessageSource 获取有关消息源的更多信息
|
||||
*/
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
@SinceMirai("0.33.0")
|
||||
class QuoteReply(val source: MessageSource) : Message, MessageMetadata, ConstrainSingle<QuoteReply> {
|
||||
companion object Key : Message.Key<QuoteReply> {
|
||||
override val typeName: String
|
||||
@ -68,7 +66,6 @@ inline val QuoteReply.id: Int
|
||||
/**
|
||||
* @see MessageSource.internalId
|
||||
*/
|
||||
@SinceMirai("0.39.2")
|
||||
@get:JvmSynthetic
|
||||
inline val QuoteReply.internalId: Int
|
||||
get() = source.internalId
|
||||
|
@ -14,8 +14,6 @@
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.PlannedRemoval
|
||||
import net.mamoe.mirai.utils.SinceMirai
|
||||
import kotlin.annotation.AnnotationTarget.*
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
@ -31,7 +29,6 @@ import kotlin.jvm.JvmSynthetic
|
||||
* @see LightApp 小程序 (JSON)
|
||||
*/
|
||||
// not using sealed class for customized implementations
|
||||
@SinceMirai("0.27.0")
|
||||
interface RichMessage : MessageContent {
|
||||
|
||||
/**
|
||||
@ -50,14 +47,12 @@ interface RichMessage : MessageContent {
|
||||
* @suppress 此 API 不稳定, 可能在任意时刻被删除
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.30.0")
|
||||
companion object Templates : Message.Key<RichMessage> {
|
||||
|
||||
/**
|
||||
* @suppress 此 API 不稳定, 可能在任意时刻被删除
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
@SinceMirai("0.30.0")
|
||||
fun share(
|
||||
url: String,
|
||||
title: String? = null,
|
||||
@ -84,20 +79,6 @@ interface RichMessage : MessageContent {
|
||||
}
|
||||
}
|
||||
|
||||
@PlannedRemoval("1.0.0")
|
||||
@JvmName("share")
|
||||
@Deprecated(
|
||||
"for binary compatibility", level = DeprecationLevel.HIDDEN
|
||||
)
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@MiraiExperimentalAPI
|
||||
fun shareDeprecated(
|
||||
url: String,
|
||||
title: String? = null,
|
||||
content: String? = null,
|
||||
coverUrl: String? = null
|
||||
): XmlMessage = share(url, title, content, coverUrl) as XmlMessage
|
||||
|
||||
override val typeName: String
|
||||
get() = "RichMessage"
|
||||
}
|
||||
@ -112,7 +93,6 @@ interface RichMessage : MessageContent {
|
||||
*
|
||||
* @see ServiceMessage 服务消息
|
||||
*/
|
||||
@SinceMirai("0.27.0")
|
||||
data class LightApp(override val content: String) : RichMessage {
|
||||
companion object Key : Message.Key<LightApp> {
|
||||
override val typeName: String get() = "LightApp"
|
||||
@ -131,7 +111,6 @@ data class LightApp(override val content: String) : RichMessage {
|
||||
*
|
||||
* @see LightApp 小程序类型消息
|
||||
*/
|
||||
@SinceMirai("0.37.3")
|
||||
open class ServiceMessage(val serviceId: Int, final override val content: String) : RichMessage {
|
||||
companion object Key : Message.Key<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 {
|
||||
businessType=0x00000001(1)
|
||||
@ -221,10 +147,9 @@ commonElem=CommonElem#750141174 {
|
||||
*/
|
||||
@Suppress("DEPRECATION_ERROR")
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.27.0")
|
||||
@MiraiExperimentalAPI
|
||||
inline fun buildXmlMessage(serviceId: Int, block: @XmlMessageDsl XmlMessageBuilder.() -> Unit): ServiceMessage =
|
||||
XmlMessage(serviceId, XmlMessageBuilder().apply(block).text)
|
||||
ServiceMessage(serviceId, XmlMessageBuilder().apply(block).text)
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@Target(CLASS, FUNCTION, TYPE)
|
||||
@ -273,8 +198,6 @@ class XmlMessageBuilder(
|
||||
sourceName = name
|
||||
sourceIconURL = iconURL
|
||||
}
|
||||
|
||||
@SinceMirai("0.27.0")
|
||||
@XmlMessageDsl
|
||||
class ItemBuilder @PublishedApi internal constructor(
|
||||
var bg: Int = 0,
|
||||
@ -297,17 +220,6 @@ class XmlMessageBuilder(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@JvmSynthetic
|
||||
@SinceMirai("0.27.0")
|
||||
@MiraiExperimentalAPI
|
||||
@Deprecated("specify serviceId explicitly", ReplaceWith("buildXmlMessage(60, block)"))
|
||||
inline fun buildXmlMessage(block: @XmlMessageDsl XmlMessageBuilder.() -> Unit): ServiceMessage =
|
||||
buildXmlMessage(60, block)
|
||||
|
||||
|
||||
@SinceMirai("0.31.0")
|
||||
@MiraiExperimentalAPI
|
||||
internal class LongMessage internal constructor(content: String, val resId: String) : ServiceMessage(35, content) {
|
||||
companion object Key : Message.Key<LongMessage> {
|
||||
@ -316,5 +228,4 @@ internal class LongMessage internal constructor(content: String, val resId: Stri
|
||||
}
|
||||
|
||||
@OptIn(MiraiExperimentalAPI::class)
|
||||
@SinceMirai("0.39.0")
|
||||
internal class ForwardMessageInternal(content: String) : ServiceMessage(35, content)
|
@ -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
|
@ -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
Loading…
Reference in New Issue
Block a user