Merge pull request #304 from mamoe/1.0.0

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

View File

@ -101,7 +101,7 @@
#### `OfflineMessageSource` 构造
可使用 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 {

View File

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

View File

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

View File

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

View File

@ -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 参数
* */

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -17,6 +17,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x388
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.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,

View File

@ -30,8 +30,8 @@ import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.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,

View File

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

View File

@ -24,11 +24,8 @@ import net.mamoe.mirai.qqandroid.utils.io.serialization.jceRequestSBuffer
import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,14 +0,0 @@
package net.mamoe.mirai.qqandroid.utils.io.serialization
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialFormat
import kotlinx.serialization.SerializationStrategy
internal interface IOFormat : SerialFormat {
fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output)
fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T
}

View File

@ -1,850 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid.utils.io.serialization
import kotlinx.io.charsets.Charset
import kotlinx.io.core.*
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.MapEntrySerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.ProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.BYTE
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.DOUBLE
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.FLOAT
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.INT
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.JCE_MAX_STRING_LENGTH
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.LIST
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.LONG
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.MAP
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.SHORT
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.SIMPLE_LIST
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRING1
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRING4
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRUCT_BEGIN
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.STRUCT_END
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.Jce.Companion.ZERO_TYPE
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceHead
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import net.mamoe.mirai.qqandroid.utils.toReadPacket
@PublishedApi
internal val CharsetGBK = Charset.forName("GBK")
@PublishedApi
internal val CharsetUTF8 = Charset.forName("UTF8")
internal enum class JceCharset(val kotlinCharset: Charset) {
GBK(Charset.forName("GBK")),
UTF8(Charset.forName("UTF8"))
}
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<JceId>(index)?.id
/**
* Jce 数据结构序列化和反序列化工具, 能将 kotlinx.serialization 通用的注解标记格式的 `class` 序列化为 [ByteArray]
*/
@Suppress("DEPRECATION_ERROR")
@OptIn(InternalSerializationApi::class)
internal class JceOld private constructor(private val charset: JceCharset, override val context: SerialModule = EmptyModule) :
SerialFormat, BinaryFormat {
private inner class ListWriter(
private val count: Int,
private val tag: Int,
private val parentEncoder: JceEncoder
) : JceEncoder(BytePacketBuilder()) {
override fun SerialDescriptor.getTag(index: Int): Int {
return 0
}
override fun endEncode(descriptor: SerialDescriptor) {
parentEncoder.writeHead(LIST, this.tag)
parentEncoder.encodeTaggedInt(0, count)
parentEncoder.output.writePacket(this.output.build())
}
}
private inner class JceMapWriter(
output: BytePacketBuilder
) : JceEncoder(output) {
override fun SerialDescriptor.getTag(index: Int): Int {
return if (index % 2 == 0) 0 else 1
}
/*
override fun endEncode(desc: SerialDescriptor) {
parentEncoder.writeHead(MAP, this.tag)
parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count)
// println(this.output.toByteArray().toUHexString())
parentEncoder.output.write(this.output.toByteArray())
}*/
override fun beginCollection(
descriptor: SerialDescriptor,
collectionSize: Int,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder {
return this
}
override fun beginStructure(
descriptor: SerialDescriptor,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder {
return this
}
}
/**
* From: com.qq.taf.jce.JceOutputStream
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
@OptIn(ExperimentalIoApi::class)
private open inner class JceEncoder(
internal val output: BytePacketBuilder
) : TaggedEncoder<Int>() {
override val context get() = this@JceOld.context
override fun SerialDescriptor.getTag(index: Int): Int {
return getSerialId(this, index) ?: error("cannot find @SerialId")
}
/**
* 序列化最开始的时候的
*/
override fun beginStructure(
descriptor: SerialDescriptor,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder =
when (descriptor.kind) {
StructureKind.LIST -> this
StructureKind.MAP -> this
StructureKind.CLASS, StructureKind.OBJECT -> this
is PolymorphicKind -> this
else -> throw SerializationException("Primitives are not supported at top-level")
}
@OptIn(ImplicitReflectionSerializer::class)
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
serializer.descriptor.kind == StructureKind.MAP -> {
try {
val entries = (value as Map<*, *>).entries
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
this.writeHead(MAP, currentTag)
this.encodeTaggedInt(0, entries.count())
SetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries)
} catch (e: Exception) {
super.encodeSerializableValue(serializer, value)
}
}
serializer.descriptor.kind == StructureKind.LIST
&& value is ByteArray -> encodeTaggedByteArray(popTag(), value as ByteArray)
serializer.descriptor.kind == StructureKind.LIST
&& serializer.descriptor.getElementDescriptor(0) is PrimitiveKind -> {
serializer.serialize(
ListWriter(
when (value) {
is ShortArray -> value.size
is IntArray -> value.size
is LongArray -> value.size
is FloatArray -> value.size
is DoubleArray -> value.size
is CharArray -> value.size
is ByteArray -> value.size
is BooleanArray -> value.size
else -> error("unknown array type: ${value.getClassName()}")
}, popTag(), this
),
value
)
}
serializer.descriptor.kind == StructureKind.LIST && value is Array<*> -> {
if (serializer.descriptor.getElementDescriptor(0).kind is PrimitiveKind.BYTE) {
encodeTaggedByteArray(popTag(), (value as Array<Byte>).toByteArray())
} else
serializer.serialize(
ListWriter((value as Array<*>).size, popTag(), this),
value
)
}
serializer.descriptor.kind == StructureKind.LIST -> {
serializer.serialize(
ListWriter((value as Collection<*>).size, popTag(), this),
value
)
}
else -> {
if (value is JceStruct) {
if (currentTagOrNull == null) {
serializer.serialize(this, value)
} else {
this.writeHead(STRUCT_BEGIN, popTag())
serializer.serialize(JceEncoder(this.output), value)
this.writeHead(STRUCT_END, 0)
}
} else if (value is ProtoBuf) {
this.encodeTaggedByteArray(popTag(), ProtoBufWithNullableSupport.dump(value))
} else {
serializer.serialize(this, value)
}
}
}
override fun encodeTaggedByte(tag: Int, value: Byte) {
if (value.toInt() == 0) {
writeHead(ZERO_TYPE, tag)
} else {
writeHead(BYTE, tag)
output.writeByte(value)
}
}
override fun encodeTaggedShort(tag: Int, value: Short) {
if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) {
encodeTaggedByte(tag, value.toByte())
} else {
writeHead(SHORT, tag)
output.writeShort(value)
}
}
override fun encodeTaggedInt(tag: Int, value: Int) {
if (value in Short.MIN_VALUE..Short.MAX_VALUE) {
encodeTaggedShort(tag, value.toShort())
} else {
writeHead(INT, tag)
output.writeInt(value)
}
}
override fun encodeTaggedFloat(tag: Int, value: Float) {
writeHead(FLOAT, tag)
output.writeFloat(value)
}
override fun encodeTaggedDouble(tag: Int, value: Double) {
writeHead(DOUBLE, tag)
output.writeDouble(value)
}
override fun encodeTaggedLong(tag: Int, value: Long) {
if (value in Int.MIN_VALUE..Int.MAX_VALUE) {
encodeTaggedInt(tag, value.toInt())
} else {
writeHead(LONG, tag)
output.writeLong(value)
}
}
override fun encodeTaggedBoolean(tag: Int, value: Boolean) {
encodeTaggedByte(tag, if (value) 1 else 0)
}
override fun encodeTaggedChar(tag: Int, value: Char) {
encodeTaggedByte(tag, value.toByte())
}
override fun encodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor, ordinal: Int) {
encodeTaggedInt(tag, ordinal)
}
override fun encodeTaggedNull(tag: Int) {
}
override fun encodeTaggedUnit(tag: Int) {
encodeTaggedNull(tag)
}
fun encodeTaggedByteArray(tag: Int, bytes: ByteArray) {
writeHead(SIMPLE_LIST, tag)
writeHead(BYTE, 0)
encodeTaggedInt(0, bytes.size)
output.writeFully(bytes)
}
override fun encodeTaggedString(tag: Int, value: String) {
require(value.length <= JCE_MAX_STRING_LENGTH) { "string is too long for tag $tag" }
val array = value.toByteArray(charset.kotlinCharset)
if (array.size > 255) {
writeHead(STRING4, tag)
output.writeInt(array.size)
output.writeFully(array)
} else {
writeHead(STRING1, tag)
output.writeByte(array.size.toByte()) // one byte
output.writeFully(array)
}
}
override fun encodeTaggedValue(tag: Int, value: Any) {
when (value) {
is Byte -> encodeTaggedByte(tag, value)
is Short -> encodeTaggedShort(tag, value)
is Int -> encodeTaggedInt(tag, value)
is Long -> encodeTaggedLong(tag, value)
is Float -> encodeTaggedFloat(tag, value)
is Double -> encodeTaggedDouble(tag, value)
is Boolean -> encodeTaggedBoolean(tag, value)
is String -> encodeTaggedString(tag, value)
is Unit -> {
}
else -> error("unsupported type: ${value.getClassName()}")
}
}
@PublishedApi
internal fun writeHead(type: Byte, tag: Int) {
if (tag < 15) {
this.output.writeByte(((tag shl 4) or type.toInt()).toByte())
return
}
if (tag < 256) {
this.output.writeByte((type.toInt() or 0xF0).toByte())
this.output.writeByte(tag.toByte())
return
}
error("tag is too large: $tag")
}
}
private open inner class JceMapReader(
val size: Int,
input: JceInput
) : JceDecoder(input) {
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
return size
}
override fun SerialDescriptor.getTag(index: Int): Int {
// 奇数 0, 即 key; 偶数 1, 即 value
return if (index % 2 == 0) 0 else 1
}
}
private open inner class JceListReader(
val size: Int,
input: JceInput
) : JceDecoder(input) {
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
return size
}
override fun SerialDescriptor.getTag(index: Int): Int {
return 0
}
}
private open inner class JceStructReader(
input: JceInput
) : JceDecoder(input) {
override fun endStructure(descriptor: SerialDescriptor) {
}
}
private open inner class NullReader(
input: JceInput
) : JceDecoder(input)
private open inner class JceDecoder(
internal val input: JceInput
) : TaggedDecoder<Int>() {
override fun SerialDescriptor.getTag(index: Int): Int {
return getSerialId(this, index) ?: error("cannot find tag with index $index")
}
override fun decodeTaggedByte(tag: Int): Byte = input.readByte(tag)
override fun decodeTaggedShort(tag: Int): Short = input.readShort(tag)
override fun decodeTaggedInt(tag: Int): Int = input.readInt(tag)
override fun decodeTaggedLong(tag: Int): Long = input.readLong(tag)
override fun decodeTaggedFloat(tag: Int): Float = input.readFloat(tag)
override fun decodeTaggedDouble(tag: Int): Double = input.readDouble(tag)
override fun decodeTaggedChar(tag: Int): Char = input.readByte(tag).toChar()
override fun decodeTaggedString(tag: Int): String = input.readString(tag)
override fun decodeTaggedBoolean(tag: Int): Boolean = input.readBoolean(tag)
override fun decodeTaggedEnum(tag: Int, enumDescription: SerialDescriptor): Int {
return input.readInt(tag)
}
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
return 0
}
/**
* [KSerializer.serialize]
*/
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
//// println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}")
when {
// 由于 Byte 的数组有两种方式写入, 需特定读取器
descriptor.kind == StructureKind.LIST
&& descriptor.getElementDescriptor(0).kind == PrimitiveKind.BYTE -> {
// ByteArray, 交给 decodeSerializableValue 进行处理
return this
}
descriptor.kind == StructureKind.LIST -> {
// if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) {
// // Array<Byte>
// return this // 交给 decodeSerializableValue
// }
val tag = currentTagOrNull
@Suppress("SENSELESS_COMPARISON") // 推断 bug
if (tag != null && input.skipToTagOrNull(tag) {
popTag()
if (it.type == SIMPLE_LIST) {
input.readHead() // list 里面元素类型, 没必要知道
}
return when (it.type) {
SIMPLE_LIST, LIST -> JceListReader(input.readInt(0), this.input)
MAP -> JceMapReader(input.readInt(0), this.input)
else -> error("type mismatch")
}
} == null && descriptor.isNullable) {
return NullReader(this.input)
}
}
descriptor.kind == StructureKind.MAP -> {
val tag = currentTagOrNull
if (tag != null) {
popTag()
}
return JceMapReader(input.readInt(0), this.input)
}
}
val tag = currentTagOrNull
val jceHead = input.peakHeadOrNull()
if (tag != null && (jceHead == null || jceHead.tag > tag)) {
return NullReader(this.input)
}
return super.beginStructure(descriptor, *typeParams)
}
override fun decodeTaggedNull(tag: Int): Nothing? {
return null
}
override fun decodeTaggedNotNullMark(tag: Int): Boolean {
return !isTagMissing(tag)
}
fun isTagMissing(tag: Int): Boolean {
val head = input.peakHeadOrNull()
return input.isEndOfInput || head == null || head.tag > tag
}
@Suppress("UNCHECKED_CAST")
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
//
println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}")
if (deserializer is NullReader) {
return null
}
currentTagOrNull?.let {
if (this.isTagMissing(it)) {
return null
}
}
when {
deserializer.descriptor == ByteArraySerializer().descriptor -> {
val tag = popTag()
return if (isTagMissing(tag)) input.readByteArrayOrNull(tag) as? T
else input.readByteArray(tag) as T
}
deserializer.descriptor.kind == StructureKind.LIST -> {
if (deserializer is ReferenceArraySerializer<*, *>
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams.isNotEmpty()
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams[0] is ByteSerializer
) {
val tag = popTag()
return if (isTagMissing(tag)) input.readByteArrayOrNull(tag)?.toTypedArray() as? T
else input.readByteArray(tag).toTypedArray() as T
} else if (deserializer is ArrayListSerializer<*>
&& (deserializer as ArrayListSerializer<*>).typeParams.isNotEmpty()
&& (deserializer as ArrayListSerializer<*>).typeParams[0] is ByteSerializer
) {
val tag = popTag()
return if (isTagMissing(tag)) input.readByteArrayOrNull(tag)?.toMutableList() as? T
else input.readByteArray(tag).toMutableList() as T
}
val tag = currentTag
// // println(tag)
@Suppress("SENSELESS_COMPARISON") // false positive
if (input.skipToTagOrNull(tag) {
return deserializer.deserialize(JceListReader(input.readInt(0), input))
} == null) {
if (isTagMissing(tag)) {
return null
} else error("property is notnull but cannot find tag $tag")
}
error("UNREACHABLE CODE")
}
deserializer.descriptor.kind == StructureKind.MAP -> {
val tag = popTag()
@Suppress("SENSELESS_COMPARISON")
if (input.skipToTagOrNull(tag) { head ->
check(head.type == MAP) { "type mismatch: ${head.type}" }
// 将 mapOf(k1 to v1, k2 to v2, ...) 转换为 listOf(k1, v1, k2, v2, ...) 以便于写入.
val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial =
MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
val setOfEntries =
SetSerializer(mapEntrySerial).deserialize(JceMapReader(input.readInt(0), input))
return setOfEntries.associateBy({ it.key }, { it.value }) as T
} == null) {
if (isTagMissing(tag)) {
return null
} else error("property is notnull but cannot find tag $tag")
}
error("UNREACHABLE CODE")
}
}
if (deserializer.descriptor.kind == StructureKind.CLASS || deserializer.descriptor.kind == StructureKind.OBJECT) {
val tag = currentTagOrNull
if (tag != null) {
@Suppress("SENSELESS_COMPARISON") // 推断 bug
if (input.skipToTagOrNull(tag) {
check(it.type == STRUCT_BEGIN) { "type mismatch: ${it.type}" }
//popTag()
return deserializer.deserialize(JceStructReader(input)).also {
while (input.input.canRead() && input.peakHeadOrNull()?.type != STRUCT_END) {
input.readHeadOrNull() ?: return@also
}
input.readHeadOrNull()
}
} == null && isTagMissing(tag)) {
return null
} else error("cannot find tag $tag")
}
return deserializer.deserialize(JceDecoder(this.input))
}
val tag = currentTagOrNull ?: return deserializer.deserialize(JceDecoder(this.input))
return if (!this.isTagMissing(tag)) {
try {
deserializer.deserialize(this)
} catch (e: Exception) {
println("exception when tag=$tag")
throw e
}
} else {
// popTag()
null
}
}
@Suppress("UNCHECKED_CAST")
override fun <T> decodeSerializableValue(deserializer: DeserializationStrategy<T>): T {
return decodeNullableSerializableValue(deserializer as DeserializationStrategy<Any?>) as? T
?: error("value with tag $currentTagOrNull(by ${deserializer.getClassName()}) is not optional but cannot find. currentJceHead = ${input.currentJceHead}")
}
}
@OptIn(ExperimentalUnsignedTypes::class)
internal inner class JceInput(
@PublishedApi
internal val input: ByteReadPacket,
maxReadSize: Long = input.remaining
) : Closeable {
private val leastRemaining = input.remaining - maxReadSize
internal val isEndOfInput: Boolean get() = input.remaining <= leastRemaining
internal var currentJceHead: JceHead? = input.doReadHead()
override fun close() = input.close()
internal fun peakHeadOrNull(): JceHead? = currentJceHead ?: readHeadOrNull()
@PublishedApi
internal fun readHead(): JceHead = readHeadOrNull() ?: error("no enough data to read head")
@PublishedApi
internal fun readHeadOrNull(): JceHead? = input.doReadHead()
/**
* 读取下一个 head 存储到 [currentJceHead]
*/
private fun ByteReadPacket.doReadHead(): JceHead? {
if (isEndOfInput) {
currentJceHead = null
// println("doReadHead: endOfInput")
return null
}
val var2 = readUByte()
val type = var2 and 15u
var tag = var2.toUInt() shr 4
if (tag == 15u) {
if (isEndOfInput) {
currentJceHead = null
// println("doReadHead: endOfInput2")
return null
}
tag = readUByte().toUInt()
}
currentJceHead = JceHead(
tag = tag.toInt(),
type = type.toByte()
)
// println("doReadHead: $currentJceHead")
return currentJceHead
}
fun readBoolean(tag: Int): Boolean =
readBooleanOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByte(tag: Int): Byte =
readByteOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readShort(tag: Int): Short =
readShortOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readLong(tag: Int): Long =
readLongOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readFloat(tag: Int): Float =
readFloatOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readDouble(tag: Int): Double =
readDoubleOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readString(tag: Int): String =
readStringOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByteArray(tag: Int): ByteArray =
readByteArrayOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
when (it.type) {
LIST -> ByteArray(readInt(0)) { readByte(0) }
SIMPLE_LIST -> {
val head = readHead()
readHead()
check(head.type.toInt() == 0) { "type mismatch, expected=0(Byte), got=${head.type}" }
input.readBytes(readInt(0))
}
else -> error("type mismatch, expected=9(List), got=${it.type}")
}
}
private fun readStringOrNull(tag: Int): String? = skipToTagOrNull(tag) { head ->
return when (head.type) {
STRING1 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
STRING4 -> input.readString(
input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } },
charset = charset.kotlinCharset
)
else -> error("type mismatch: ${head.type}, expecting 6 or 7 (for string)")
}
}
private fun readLongOrNull(tag: Int): Long? = skipToTagOrNull(tag) {
return when (it.type) {
ZERO_TYPE -> 0
BYTE -> input.readByte().toLong()
SHORT -> input.readShort().toLong()
INT -> input.readInt().toLong()
LONG -> input.readLong()
else -> error("type mismatch ${it.type} when reading tag $tag")
}
}
private fun readShortOrNull(tag: Int): Short? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toShort()
1 -> input.readShort()
else -> error("type mismatch: ${it.type}")
}
}
private fun readIntOrNull(tag: Int): Int? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte().toInt()
1 -> input.readShort().toInt()
2 -> input.readInt()
else -> error("type mismatch: ${it.type}")
}
}
private fun readByteOrNull(tag: Int): Byte? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0
0 -> input.readByte()
else -> error("type mismatch")
}
}
private fun readFloatOrNull(tag: Int): Float? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0f
4 -> input.readFloat()
else -> error("type mismatch: ${it.type}")
}
}
private fun readDoubleOrNull(tag: Int): Double? = skipToTagOrNull(tag) {
return when (it.type.toInt()) {
12 -> 0.0
4 -> input.readFloat().toDouble()
5 -> input.readDouble()
else -> error("type mismatch: ${it.type}")
}
}
private fun readBooleanOrNull(tag: Int): Boolean? = this.readByteOrNull(tag)?.let { it.toInt() != 0 }
private fun skipField() {
skipField(readHead().type)
}
private fun skipToStructEnd() {
var head: JceHead
do {
head = readHead()
skipField(head.type)
} while (head.type.toInt() != 11)
}
@OptIn(ExperimentalUnsignedTypes::class)
@PublishedApi
internal fun skipField(type: Byte) = when (type.toInt()) {
0 -> this.input.discardExact(1)
1 -> this.input.discardExact(2)
2 -> this.input.discardExact(4)
3 -> this.input.discardExact(8)
4 -> this.input.discardExact(4)
5 -> this.input.discardExact(8)
6 -> this.input.discardExact(this.input.readUByte().toInt())
7 -> this.input.discardExact(this.input.readInt())
8 -> { // map
repeat(this.readInt(0) * 2) {
skipField()
}
}
9 -> { // list
repeat(this.readInt(0)) {
skipField()
}
}
10 -> this.skipToStructEnd()
11, 12 -> {
}
13 -> {
val head = readHead()
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
this.input.discardExact(this.readInt(0))
}
else -> error("invalid type: $type")
}
}
@Suppress("MemberVisibilityCanBePrivate")
companion object {
val UTF8 =
JceOld(JceCharset.UTF8)
val GBK =
JceOld(JceCharset.GBK)
fun byCharSet(c: JceCharset): JceOld {
return if (c == JceCharset.UTF8) {
UTF8
} else {
GBK
}
}
private fun Any?.getClassName(): String =
(if (this == null) Unit::class else this::class).qualifiedName?.split(".")?.takeLast(2)?.joinToString(".")
?: "<unnamed class>"
}
fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket {
val encoder = BytePacketBuilder()
val dumper = JceEncoder(encoder)
dumper.encode(serializer, obj)
return encoder.build()
}
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
return dumpAsPacket(serializer, value).readBytes()
}
/**
* 注意 close [packet]!!
*/
fun <T> load(
deserializer: DeserializationStrategy<T>,
packet: ByteReadPacket,
length: Int = packet.remaining.toInt()
): T {
return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer)
}
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
return bytes.toReadPacket().use {
val decoder = JceDecoder(JceInput(it))
decoder.decode(deserializer)
}
}
}
internal inline fun <R> JceOld.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
// println("skipping to $tag start")
while (true) {
if (isEndOfInput) { // 读不了了
currentJceHead = null
// println("skipping to $tag: endOfInput")
return null
}
var head = currentJceHead
if (head == null) { // 没有新的 head 了
head = readHeadOrNull() ?: return null
}
if (head.tag > tag) {
// println("skipping to $tag: head.tag > tag")
return null
}
// readHead()
if (head.tag == tag) {
// readHeadOrNull()
currentJceHead = null
// println("skipping to $tag: run block")
return block(head)
}
// println("skipping to $tag: tag not matching")
// println("skipping to $tag: skipField")
this.skipField(head.type)
currentJceHead = readHeadOrNull()
}
}

View File

@ -23,10 +23,13 @@ import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoBuf
import kotlinx.serialization.protobuf.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

View File

@ -1,10 +0,0 @@
# io.serialization
**序列化支持**
包含:
- QQ 的 JceStruct 相关的全自动序列化和反序列化: [Jce.kt](jce/JceNew.kt)
- Protocol Buffers 的 optional 支持: [ProtoBufWithNullableSupport.kt](ProtoBufWithNullableSupport.kt)
其中, `ProtoBufWithNullableSupport` 的绝大部分源码来自 `kotlinx.serialization`. 原著权归该项目作者所有.
Mirai 所做的修改已经标记上了 `MIRAI MODIFY START`

View File

@ -1,330 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("PrivatePropertyName")
package net.mamoe.mirai.qqandroid.utils.io.serialization.jce
import kotlinx.serialization.*
import kotlinx.serialization.builtins.AbstractDecoder
import kotlinx.serialization.internal.TaggedDecoder
import kotlinx.serialization.modules.SerialModule
@OptIn(InternalSerializationApi::class) // 将来 kotlinx 修改后再复制过来 mirai.
internal class JceDecoder(
val jce: JceInput, override val context: SerialModule
) : TaggedDecoder<JceTag>() {
override val updateMode: UpdateMode
get() = UpdateMode.BANNED
override fun SerialDescriptor.getTag(index: Int): JceTag {
val annotations = this.getElementAnnotations(index)
val id = annotations.filterIsInstance<JceId>().single().id
// ?: error("cannot find @JceId or @ProtoId for ${this.getElementName(index)} in ${this.serialName}")
//println("getTag: ${this.getElementName(index)}=$id")
return JceTagCommon(id)
}
private fun SerialDescriptor.getJceTagId(index: Int): Int {
// higher performance, don't use filterIsInstance
val annotation = getElementAnnotations(index).firstOrNull { it is JceId }
?: error("missing @JceId for ${getElementName(index)} in ${this.serialName}")
return (annotation as JceId).id
}
private val SimpleByteArrayReader: SimpleByteArrayReaderImpl = SimpleByteArrayReaderImpl()
private inner class SimpleByteArrayReaderImpl : AbstractDecoder() {
override fun decodeSequentially(): Boolean = true
override fun endStructure(descriptor: SerialDescriptor) {
this@JceDecoder.endStructure(descriptor)
}
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
this@JceDecoder.pushTag(JceTagListElement)
return this@JceDecoder.beginStructure(descriptor, *typeParams)
}
override fun decodeByte(): Byte = jce.input.readByte()
override fun decodeShort(): Short = error("illegal access")
override fun decodeInt(): Int = error("illegal access")
override fun decodeLong(): Long = error("illegal access")
override fun decodeFloat(): Float = error("illegal access")
override fun decodeDouble(): Double = error("illegal access")
override fun decodeBoolean(): Boolean = error("illegal access")
override fun decodeChar(): Char = error("illegal access")
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = error("illegal access")
override fun decodeString(): String = error("illegal access")
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
error("should not be reached")
}
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
// 不要读下一个 head
return jce.currentHead.let { jce.readJceIntValue(it) }
}
}
private val ListReader: ListReaderImpl = ListReaderImpl()
private inner class ListReaderImpl : AbstractDecoder() {
override fun decodeSequentially(): Boolean = true
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("should not be reached")
override fun endStructure(descriptor: SerialDescriptor) {
this@JceDecoder.endStructure(descriptor)
}
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
this@JceDecoder.pushTag(JceTagListElement)
return this@JceDecoder.beginStructure(descriptor, *typeParams)
}
override fun decodeByte(): Byte = jce.useHead { jce.readJceByteValue(it) }
override fun decodeShort(): Short = jce.useHead { jce.readJceShortValue(it) }
override fun decodeInt(): Int = jce.useHead { jce.readJceIntValue(it) }
override fun decodeLong(): Long = jce.useHead { jce.readJceLongValue(it) }
override fun decodeFloat(): Float = jce.useHead { jce.readJceFloatValue(it) }
override fun decodeDouble(): Double = jce.useHead { jce.readJceDoubleValue(it) }
override fun decodeBoolean(): Boolean = jce.useHead { jce.readJceBooleanValue(it) }
override fun decodeChar(): Char = decodeByte().toChar()
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt()
override fun decodeString(): String = jce.useHead { jce.readJceStringValue(it) }
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
//println("decodeCollectionSize: ${descriptor.serialName}")
// 不读下一个 head
return jce.useHead { jce.readJceIntValue(it) }
}
}
private val MapReader: MapReaderImpl = MapReaderImpl()
private inner class MapReaderImpl : AbstractDecoder() {
override fun decodeSequentially(): Boolean = true
override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("stub")
override fun endStructure(descriptor: SerialDescriptor) {
this@JceDecoder.endStructure(descriptor)
}
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
println { "MapReader.beginStructure: ${jce.currentHead}" }
this@JceDecoder.pushTag(
when (jce.currentHead.tag) {
0 -> JceTagMapEntryKey
1 -> JceTagMapEntryValue
else -> error("illegal map entry head: ${jce.currentHead.tag}")
}
)
return this@JceDecoder.beginStructure(descriptor, *typeParams)
}
override fun decodeByte(): Byte = jce.useHead { jce.readJceByteValue(it) }
override fun decodeShort(): Short = jce.useHead { jce.readJceShortValue(it) }
override fun decodeInt(): Int = jce.useHead { jce.readJceIntValue(it) }
override fun decodeLong(): Long = jce.useHead { jce.readJceLongValue(it) }
override fun decodeFloat(): Float = jce.useHead { jce.readJceFloatValue(it) }
override fun decodeDouble(): Double = jce.useHead { jce.readJceDoubleValue(it) }
override fun decodeBoolean(): Boolean = jce.useHead { jce.readJceBooleanValue(it) }
override fun decodeChar(): Char = decodeByte().toChar()
override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt()
override fun decodeString(): String = jce.useHead { jce.readJceStringValue(it) }
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
println { "decodeCollectionSize in MapReader: ${descriptor.serialName}" }
// 不读下一个 head
return jce.useHead { jce.readJceIntValue(it) }
}
}
override fun endStructure(descriptor: SerialDescriptor) {
structureHierarchy--
println { "endStructure: ${descriptor.serialName}" }
if (currentTagOrNull?.isSimpleByteArray == true) {
jce.prepareNextHead() // read to next head
}
if (descriptor.kind == StructureKind.CLASS) {
if (currentTagOrNull == null) {
return
}
while (true) {
val currentHead = jce.currentHeadOrNull ?: return
if (currentHead.type == Jce.STRUCT_END) {
jce.prepareNextHead()
//println("current end")
break
}
//println("current $currentHead")
jce.skipField(currentHead.type)
jce.prepareNextHead()
}
// pushTag(JceTag(0, true))
// skip STRUCT_END
// popTag()
}
}
companion object {
@Suppress("MemberVisibilityCanBePrivate")
var debuggingMode: Boolean = false
var structureHierarchy: Int = 0
inline fun println(value: () -> String) {
if (debuggingMode) {
kotlin.io.println(" ".repeat(structureHierarchy) + value())
}
}
@Suppress("NOTHING_TO_INLINE")
inline fun println(value: Any? = "") {
if (debuggingMode) {
kotlin.io.println(" ".repeat(structureHierarchy) + value)
}
}
}
override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
println()
println { "beginStructure: ${descriptor.serialName}" }
structureHierarchy++
return when (descriptor.kind) {
is PrimitiveKind -> this@JceDecoder
StructureKind.MAP -> {
//println("!! MAP")
val tag = popTag()
return jce.skipToHeadAndUseIfPossibleOrFail(tag.id) {
it.checkType(Jce.MAP, "beginStructure", tag, descriptor)
MapReader
}
}
StructureKind.LIST -> {
//println("!! ByteArray")
//println("decoderTag: $currentTagOrNull")
//println("jceHead: " + jce.currentHeadOrNull)
return jce.skipToHeadAndUseIfPossibleOrFail(currentTag.id) {
// don't check type. it's polymorphic
//println("listHead: $it")
when (it.type) {
Jce.SIMPLE_LIST -> {
currentTag.isSimpleByteArray = true
jce.nextHead() // 无用的元素类型
SimpleByteArrayReader
}
Jce.LIST -> ListReader
else -> error("type mismatch. Expected SIMPLE_LIST or LIST, got $it instead")
}
}
}
StructureKind.CLASS -> {
currentTagOrNull ?: return this@JceDecoder // outermost
//println("!! CLASS")
//println("decoderTag: $currentTag")
//println("jceHead: " + jce.currentHeadOrNull)
val tag = popTag()
return jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jceHead ->
jceHead.checkType(Jce.STRUCT_BEGIN, "beginStructure", tag, descriptor)
repeat(descriptor.elementsCount) {
pushTag(descriptor.getTag(descriptor.elementsCount - it - 1)) // better performance
}
this // independent tag stack
}
}
StructureKind.OBJECT -> error("unsupported StructureKind.OBJECT: ${descriptor.serialName}")
is UnionKind -> error("unsupported UnionKind: ${descriptor.serialName}")
is PolymorphicKind -> error("unsupported PolymorphicKind: ${descriptor.serialName}")
}
}
override fun decodeSequentially(): Boolean = false
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
var jceHead = jce.currentHeadOrNull ?: kotlin.run {
println("decodeElementIndex: currentHead == null")
return CompositeDecoder.READ_DONE
}
println { "decodeElementIndex: ${jce.currentHead}" }
while (!jce.input.endOfInput) {
if (jceHead.type == Jce.STRUCT_END) {
println { "decodeElementIndex: ${jce.currentHead}" }
return CompositeDecoder.READ_DONE
}
repeat(descriptor.elementsCount) {
val tag = descriptor.getJceTagId(it)
if (tag == jceHead.tag) {
println {
"name=" + descriptor.getElementName(
it
)
}
return it
}
}
jce.skipField(jceHead.type)
if (!jce.prepareNextHead()) {
println { "decodeElementIndex EOF" }
break
}
jceHead = jce.currentHead
println { "next! $jceHead" }
}
return CompositeDecoder.READ_DONE // optional support
}
override fun decodeTaggedInt(tag: JceTag): Int =
kotlin.runCatching { jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceIntValue(it) } }.getOrElse {
throw IllegalStateException("$tag", it)
}
override fun decodeTaggedByte(tag: JceTag): Byte =
kotlin.runCatching { jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceByteValue(it) } }.getOrElse {
throw IllegalStateException("$tag", it)
}
override fun decodeTaggedBoolean(tag: JceTag): Boolean =
jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceBooleanValue(it) }
override fun decodeTaggedFloat(tag: JceTag): Float =
jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceFloatValue(it) }
override fun decodeTaggedDouble(tag: JceTag): Double =
jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceDoubleValue(it) }
override fun decodeTaggedShort(tag: JceTag): Short =
jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceShortValue(it) }
override fun decodeTaggedLong(tag: JceTag): Long =
jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceLongValue(it) }
override fun decodeTaggedString(tag: JceTag): String =
jce.skipToHeadAndUseIfPossibleOrFail(tag.id) { jce.readJceStringValue(it) }
override fun decodeTaggedNotNullMark(tag: JceTag): Boolean {
return jce.skipToHeadOrNull(tag.id) != null
}
}

View File

@ -1,263 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid.utils.io.serialization.jce
import kotlinx.io.core.*
import net.mamoe.mirai.qqandroid.utils.io.readString
import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset
import net.mamoe.mirai.qqandroid.utils.toUHexString
/**
* Jce Input. 需要手动管理 head.
*/
internal class JceInput(
val input: Input, val charset: JceCharset
) {
private var _head: JceHead? = null
val currentHead: JceHead get() = _head ?: throw EOFException("No current JceHead available")
val currentHeadOrNull: JceHead? get() = _head
init {
prepareNextHead()
}
/**
* 读取下一个 [JceHead] 并保存. 可通过 [currentHead] 获取这个 [JceHead].
*
* @return 是否成功读取. 返回 `false` 时代表 [Input.endOfInput]
*/
fun prepareNextHead(): Boolean {
return readNextHeadButDoNotAssignTo_Head().also { _head = it; } != null
}
fun nextHead(): JceHead {
if (!prepareNextHead()) {
throw EOFException("No more JceHead available")
}
return currentHead
}
/**
* 直接读取下一个 [JceHead] 并返回.
* 返回 `null` 则代表 [Input.endOfInput]
*/
@Suppress("FunctionName")
@OptIn(ExperimentalUnsignedTypes::class)
private fun readNextHeadButDoNotAssignTo_Head(): JceHead? {
if (input.endOfInput) {
return null
}
val var2 = input.readUByte()
val type = var2 and 15u
var tag = var2.toUInt() shr 4
if (tag == 15u) {
tag = input.readUByte().toUInt()
}
return JceHead(
tag = tag.toInt(),
type = type.toByte()
)
}
/**
* 使用这个 [JceHead].
* [block] 结束后将会 [准备下一个 [JceHead]][prepareNextHead]
*/
inline fun <R> useHead(crossinline block: (JceHead) -> R): R {
return currentHead.let(block).also { prepareNextHead() }
}
/**
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则返回 `null`
*/
inline fun <R> skipToHeadAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? {
return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() }
}
/**
* 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常
*/
inline fun <R : Any> skipToHeadAndUseIfPossibleOrFail(
tag: Int,
crossinline message: () -> String = { "tag not found: $tag" },
crossinline block: (JceHead) -> R
): R {
return checkNotNull<R>(skipToHeadAndUseIfPossibleOrNull(tag, block), message)
}
tailrec fun skipToHeadOrNull(tag: Int): JceHead? {
val current: JceHead = currentHeadOrNull ?: return null // no backing field
return when {
current.tag > tag -> null // tag 大了,即找不到
current.tag == tag -> current // 满足需要.
else -> { // tag 小了
skipField(current.type)
check(prepareNextHead()) { "cannot skip to tag $tag, early EOF" }
skipToHeadOrNull(tag)
}
}
}
inline fun skipToHeadOrFail(
tag: Int,
message: () -> String = { "head not found: $tag" }
): JceHead {
return checkNotNull(skipToHeadOrNull(tag), message)
}
@OptIn(ExperimentalUnsignedTypes::class)
@PublishedApi
internal fun skipField(type: Byte): Unit {
JceDecoder.println {
"skipping ${JceHead.findJceTypeName(
type
)}"
}
when (type) {
Jce.BYTE -> this.input.discardExact(1)
Jce.SHORT -> this.input.discardExact(2)
Jce.INT -> this.input.discardExact(4)
Jce.LONG -> this.input.discardExact(8)
Jce.FLOAT -> this.input.discardExact(4)
Jce.DOUBLE -> this.input.discardExact(8)
Jce.STRING1 -> this.input.discardExact(this.input.readUByte().toInt())
Jce.STRING4 -> this.input.discardExact(this.input.readInt())
Jce.MAP -> { // map
JceDecoder.structureHierarchy++
var count: Int = 0
nextHead() // avoid shadowing, don't remove
repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping map" }) {
readJceIntValue(it).also { count = it * 2 }
} * 2) {
skipField(currentHead.type)
if (it != count - 1) { // don't read last head
nextHead()
}
}
JceDecoder.structureHierarchy--
}
Jce.LIST -> { // list
JceDecoder.structureHierarchy++
var count: Int = 0
nextHead() // avoid shadowing, don't remove
repeat(skipToHeadAndUseIfPossibleOrFail(0, message = { "tag 0 not found when skipping list" }) { head ->
readJceIntValue(head).also { count = it }
}) {
skipField(currentHead.type)
if (it != count - 1) { // don't read last head
nextHead()
}
}
JceDecoder.structureHierarchy--
}
Jce.STRUCT_BEGIN -> {
JceDecoder.structureHierarchy++
var head: JceHead
do {
head = nextHead()
skipField(head.type)
} while (head.type != Jce.STRUCT_END)
JceDecoder.structureHierarchy--
}
Jce.STRUCT_END, Jce.ZERO_TYPE -> {
}
Jce.SIMPLE_LIST -> {
JceDecoder.structureHierarchy++
var head = nextHead()
check(head.type == Jce.BYTE) { "bad simple list element type: " + head.type }
check(head.tag == 0) { "simple list element tag must be 0, but was ${head.tag}" }
head = nextHead()
check(head.tag == 0) { "tag for size for simple list must be 0, but was ${head.tag}" }
this.input.discardExact(readJceIntValue(head))
JceDecoder.structureHierarchy--
}
else -> error("invalid type: $type")
}
}
// region readers
fun readJceIntValue(head: JceHead): Int {
//println("readJceIntValue: $head")
return when (head.type) {
Jce.ZERO_TYPE -> 0
Jce.BYTE -> input.readByte().toInt()
Jce.SHORT -> input.readShort().toInt()
Jce.INT -> input.readInt()
else -> error("type mismatch: $head, remaining=${input.readBytes().toUHexString()}")
}
}
fun readJceShortValue(head: JceHead): Short {
return when (head.type) {
Jce.ZERO_TYPE -> 0
Jce.BYTE -> input.readByte().toShort()
Jce.SHORT -> input.readShort()
else -> error("type mismatch: $head")
}
}
fun readJceLongValue(head: JceHead): Long {
return when (head.type) {
Jce.ZERO_TYPE -> 0
Jce.BYTE -> input.readByte().toLong()
Jce.SHORT -> input.readShort().toLong()
Jce.INT -> input.readInt().toLong()
Jce.LONG -> input.readLong()
else -> error("type mismatch ${head.type}")
}
}
fun readJceByteValue(head: JceHead): Byte {
//println("readJceByteValue: $head")
return when (head.type) {
Jce.ZERO_TYPE -> 0
Jce.BYTE -> input.readByte()
else -> error("type mismatch: $head")
}
}
fun readJceFloatValue(head: JceHead): Float {
return when (head.type) {
Jce.ZERO_TYPE -> 0f
Jce.FLOAT -> input.readFloat()
else -> error("type mismatch: $head")
}
}
@OptIn(ExperimentalUnsignedTypes::class)
fun readJceStringValue(head: JceHead): String {
//println("readJceStringValue: $head")
return when (head.type) {
Jce.STRING1 -> input.readString(input.readUByte().toInt(), charset = charset.kotlinCharset)
Jce.STRING4 -> input.readString(
input.readUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } },
charset = charset.kotlinCharset
)
else -> error("type mismatch: $head, expecting 6 or 7 (for string)")
}
}
fun readJceDoubleValue(head: JceHead): Double {
return when (head.type.toInt()) {
12 -> 0.0
4 -> input.readFloat().toDouble()
5 -> input.readDouble()
else -> error("type mismatch: $head")
}
}
fun readJceBooleanValue(head: JceHead): Boolean {
return readJceByteValue(head) == 1.toByte()
}
}

View File

@ -1,84 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid.utils.io.serialization.jce
import kotlinx.io.core.*
import kotlinx.serialization.BinaryFormat
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialFormat
import kotlinx.serialization.SerializationStrategy
import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule
import net.mamoe.mirai.qqandroid.utils.io.serialization.IOFormat
import net.mamoe.mirai.qqandroid.utils.io.serialization.JceCharset
import net.mamoe.mirai.qqandroid.utils.io.serialization.JceOld
import net.mamoe.mirai.qqandroid.utils.toReadPacket
/**
* Jce 数据结构序列化和反序列化器.
*
* @author Him188
*/
internal class Jce(
override val context: SerialModule,
val charset: JceCharset
) : SerialFormat, IOFormat, BinaryFormat {
override fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output) {
output.writePacket(JceOld.byCharSet(this.charset).dumpAsPacket(serializer, ojb))
}
override fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T {
return JceDecoder(
JceInput(
input,
charset
), context
).decodeSerializableValue(deserializer)
}
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
return buildPacket { dumpTo(serializer, value, this) }.readBytes()
}
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T {
return load(deserializer, bytes.toReadPacket())
}
companion object {
val UTF_8 = Jce(
EmptyModule,
JceCharset.UTF8
)
val GBK = Jce(
EmptyModule,
JceCharset.GBK
)
fun byCharSet(c: JceCharset): Jce {
return if (c == JceCharset.UTF8) UTF_8 else GBK
}
internal const val BYTE: Byte = 0
internal const val DOUBLE: Byte = 5
internal const val FLOAT: Byte = 4
internal const val INT: Byte = 2
internal const val JCE_MAX_STRING_LENGTH = 104857600
internal const val LIST: Byte = 9
internal const val LONG: Byte = 3
internal const val MAP: Byte = 8
internal const val SHORT: Byte = 1
internal const val SIMPLE_LIST: Byte = 13
internal const val STRING1: Byte = 6
internal const val STRING4: Byte = 7
internal const val STRUCT_BEGIN: Byte = 10
internal const val STRUCT_END: Byte = 11
internal const val ZERO_TYPE: Byte = 12
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid.utils.io.serialization.jce
import kotlinx.io.core.Output
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerialInfo
/**
* 标注 JCE 序列化时使用的 ID
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
internal annotation class JceId(val id: Int)
/**
* 类中元素的 tag
*
* 保留这个结构, 为将来增加功能的兼容性.
*/
@PublishedApi
internal abstract class JceTag {
abstract val id: Int
internal var isSimpleByteArray: Boolean = false
}
internal object JceTagListElement : JceTag() {
override val id: Int get() = 0
override fun toString(): String {
return "JceTagListElement"
}
}
internal object JceTagMapEntryKey : JceTag() {
override val id: Int get() = 0
override fun toString(): String {
return "JceTagMapEntryKey"
}
}
internal object JceTagMapEntryValue : JceTag() {
override val id: Int get() = 1
override fun toString(): String {
return "JceTagMapEntryValue"
}
}
internal data class JceTagCommon(
override val id: Int
) : JceTag()
internal fun JceHead.checkType(type: Byte, message: String, tag: JceTag, descriptor: SerialDescriptor) {
check(this.type == type) {
"type mismatch. " +
"Expected ${JceHead.findJceTypeName(type)}, " +
"actual ${JceHead.findJceTypeName(this.type)} for $message. " +
"Tag info: " +
"id=${tag.id}, " +
"name=${descriptor.getElementName(tag.id)} " +
"in ${descriptor.serialName}" }
}
@PublishedApi
internal fun Output.writeJceHead(type: Byte, tag: Int) {
if (tag < 15) {
writeByte(((tag shl 4) or type.toInt()).toByte())
return
}
if (tag < 256) {
writeByte((type.toInt() or 0xF0).toByte())
writeByte(tag.toByte())
return
}
error("tag is too large: $tag")
}
@OptIn(ExperimentalUnsignedTypes::class)
inline class JceHead(private val value: Long) {
constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
val tag: Int get() = (value ushr 32).toInt()
val type: Byte get() = value.toUInt().toByte()
override fun toString(): String {
return "JceHead(tag=$tag, type=$type(${findJceTypeName(type)}))"
}
companion object {
fun findJceTypeName(type: Byte): String {
return when (type) {
Jce.BYTE -> "Byte"
Jce.DOUBLE -> "Double"
Jce.FLOAT -> "Float"
Jce.INT -> "Int"
Jce.LIST -> "List"
Jce.LONG -> "Long"
Jce.MAP -> "Map"
Jce.SHORT -> "Short"
Jce.SIMPLE_LIST -> "SimpleList"
Jce.STRING1 -> "String1"
Jce.STRING4 -> "String4"
Jce.STRUCT_BEGIN -> "StructBegin"
Jce.STRUCT_END -> "StructEnd"
Jce.ZERO_TYPE -> "Zero"
else -> error("illegal jce type: $type")
}
}
}
}

View File

@ -16,15 +16,14 @@ import kotlinx.io.core.*
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 号或群号.

View File

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

View File

@ -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 {
/**
* 请求头像下载链接
*/

View File

@ -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 {
/**
* 入群公告, 没有时为空字符串.

View File

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

View File

@ -19,19 +19,19 @@ import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.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()

View File

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

View File

@ -1,74 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.PlannedRemoval
import kotlin.jvm.JvmSynthetic
/**
* QQ 对象.
*
* 0.39.0 mirai 引入 [User] 作为 [Friend] [Member] 的父类,
* 以备将来支持仅 [Friend] 可用的 API, 如设置备注.
*
* 所有 API 均有二进制兼容.
*
* 请根据实际情况, 使用 [Friend] [User] 替代.
*/
@PlannedRemoval("1.0.0")
@Deprecated(
"use Friend or Person instead",
replaceWith = ReplaceWith("Friend", "net.mamoe.mirai.contact.Friend"),
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
abstract class QQ : User(), CoroutineScope {
/**
* QQ 号码
*/
abstract override val id: Long
/**
* 昵称
*/
abstract override val nick: String
/**
* 头像下载链接
*/
override val avatarUrl: String
get() = "http://q1.qlogo.cn/g?b=qq&nk=$id&s=640"
/**
* 向这个对象发送消息.
*
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
* @see GroupMessageSendEvent 发送群消息事件. cancellable
*
* @throws EventCancelledException 当发送消息事件被取消时抛出
* @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出
* @throws MessageTooLargeException 当消息过长时抛出
*
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
*/
@JvmSynthetic
abstract override suspend fun sendMessage(message: Message): MessageReceipt<QQ>
}

View File

@ -1,55 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmMultifileClass
@file:JvmName("BotHelperKt")
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@MiraiExperimentalAPI
@Suppress("ClassName")
sealed class AddFriendResult {
abstract class DONE internal constructor() : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Done)"
}
/**
* 对方拒绝添加好友
*/
object REJECTED : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Rejected)"
}
/**
* 这个人已经是好友
*/
object ALREADY_ADDED : DONE() {
override fun toString(): String = "AddFriendResult(AlreadyAdded)"
}
/**
* 等待对方同意
*/
object WAITING_FOR_APPROVAL : DONE() {
override fun toString(): String = "AddFriendResult(WaitingForApproval)"
}
/**
* 成功添加 (只在对方设置为允许任何人直接添加为好友时才会获得这个结果)
*/
object ADDED : DONE() {
override fun toString(): String = "AddFriendResult(Added)"
}
}

View File

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

View File

@ -1,26 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI
/**
* 曾用名列表
*
* 曾用名可能是:
* - 昵称
* - 共同群内的群名片
*/
@MiraiExperimentalAPI
class PreviousNameList(
list: List<String>
) : List<String> by list {
override fun toString(): String = this.joinToString(prefix = "PreviousNameList(", postfix = ")", separator = ", ")
}

View File

@ -11,9 +11,7 @@
package net.mamoe.mirai.data
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)
}
}*/

View File

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

View File

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

View File

@ -25,7 +25,9 @@ import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.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,
/**

View File

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

View File

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

View File

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

View File

@ -0,0 +1,106 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.event
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.BotEvent
import kotlin.coroutines.resume
import kotlin.jvm.JvmSynthetic
import kotlin.reflect.KClass
/**
* 挂起当前协程, 直到监听到事件 [E] 的广播, 返回这个事件实例.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制.
*
* @see subscribe 普通地监听一个事件
* @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值
*
* @throws TimeoutCancellationException 在超时后抛出.
*/
@JvmSynthetic
suspend inline fun <reified E : Event> nextEvent(
timeoutMillis: Long = -1
): E {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
return withTimeoutOrCoroutineScope(timeoutMillis) {
nextEventImpl(E::class, this)
}
}
/**
* 挂起当前协程, 直到监听到事件 [E] 的广播, 返回这个事件实例.
* 将筛选 [BotEvent.bot] [this] 相等的事件.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制.
*
* @see subscribe 普通地监听一个事件
* @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值
*
* @throws TimeoutCancellationException 在超时后抛出.
*/
@JvmSynthetic
suspend inline fun <reified E : BotEvent> Bot.nextEvent(
timeoutMillis: Long = -1
): E {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" }
return withTimeoutOrCoroutineScope(timeoutMillis) {
nextBotEventImpl(this@nextEvent, E::class, this)
}
}
@JvmSynthetic
@PublishedApi
internal suspend inline fun <E : Event> nextEventImpl(
eventClass: KClass<E>,
coroutineScope: CoroutineScope
): E = suspendCancellableCoroutine { cont ->
coroutineScope.subscribe(eventClass) {
try {
cont.resume(this)
} catch (e: Exception) {
}
return@subscribe ListeningStatus.STOPPED
}
}
@JvmSynthetic
@PublishedApi
internal suspend inline fun <E : BotEvent> nextBotEventImpl(
bot: Bot,
eventClass: KClass<E>,
coroutineScope: CoroutineScope
): E = suspendCancellableCoroutine { cont ->
coroutineScope.subscribe(eventClass) {
try {
if (this.bot == bot) cont.resume(this)
} catch (e: Exception) {
}
return@subscribe ListeningStatus.STOPPED
}
}
@JvmSynthetic
@PublishedApi
internal suspend inline fun <R> withTimeoutOrCoroutineScope(
timeoutMillis: Long,
noinline block: suspend CoroutineScope.() -> R
): R {
require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " }
return if (timeoutMillis == -1L) {
coroutineScope(block)
} else {
withTimeout(timeoutMillis, block)
}
}

View File

@ -12,13 +12,12 @@
package net.mamoe.mirai.event
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

View File

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

View File

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

View File

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

View File

@ -1,438 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress(
"EXPERIMENTAL_UNSIGNED_LITERALS",
"EXPERIMENTAL_API_USAGE",
"unused",
"INVISIBLE_REFERENCE",
"INVISIBLE_MEMBER"
)
@file:OptIn(MiraiInternalAPI::class)
package net.mamoe.mirai.message
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.async
import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/**
* 一条消息事件.
* 它是一个 [BotEvent], 因此可以被 [监听][Bot.subscribe]
*
* 支持的消息类型:
* [GroupMessage]
* [FriendMessage]
*
* @see isContextIdenticalWith 判断语境是否相同
*/
@Suppress("DEPRECATION")
@SinceMirai("0.32.0")
abstract class ContactMessage : MessagePacket<User, Contact>(), BotEvent
/**
* 一条从服务器接收到的消息事件.
* 请查看各平台的 `actual` 实现的说明.
*/
@Suppress("DEPRECATION")
@Deprecated(
message = "use ContactMessage",
replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage")
)
expect abstract class MessagePacket<TSender : User, TSubject : Contact> constructor() :
MessagePacketBase<TSender, TSubject>
/**
* 仅内部使用, 请使用 [ContactMessage]
*/ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构
@Deprecated(
message = "use ContactMessage",
replaceWith = ReplaceWith("ContactMessage", "net.mamoe.mirai.message.ContactMessage")
)
@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST")
abstract class MessagePacketBase<out TSender : User, out TSubject : Contact> : Packet, BotEvent, AbstractEvent() {
/**
* 接受到这条消息的
*/
@WeakRefProperty
abstract override val bot: Bot
/**
* 消息事件主体.
*
* 对于好友消息, 这个属性为 [QQ] 的实例, [sender] 引用相同;
* 对于群消息, 这个属性为 [Group] 的实例, [GroupMessage.group] 引用相同
*
* 在回复消息时, 可通过 [subject] 作为回复对象
*/
@WeakRefProperty
abstract val subject: TSubject
/**
* 发送人.
*
* 在好友消息时为 [QQ] 的实例, 在群消息时为 [Member] 的实例
*/
@WeakRefProperty
abstract val sender: TSender
abstract val senderName: String
/**
* 消息内容
*/
abstract val message: MessageChain
/**
* 消息发送时间 (由服务器提供)
*/
@SinceMirai("0.39.0")
abstract val time: Int
/**
* 消息源
*/
open val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming
// region 发送 Message
/**
* 给这个消息事件的主体发送消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
suspend inline fun reply(message: Message): MessageReceipt<TSubject> =
subject.sendMessage(message.asMessageChain()) as MessageReceipt<TSubject>
suspend inline fun reply(plain: String): MessageReceipt<TSubject> =
subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt<TSubject>
// endregion
// region 图片
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
suspend inline fun ExternalImage.send(): MessageReceipt<TSubject> = this.sendTo(subject)
suspend inline fun Image.send(): MessageReceipt<TSubject> = this.sendTo(subject)
suspend inline fun Message.send(): MessageReceipt<TSubject> = this.sendTo(subject)
suspend inline fun String.send(): MessageReceipt<TSubject> = this.toMessage().sendTo(subject)
// endregion
// region 引用回复
/**
* 给这个消息事件的主体发送引用回复消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
suspend inline fun quoteReply(message: MessageChain): MessageReceipt<TSubject> =
reply(this.message.quote() + message)
suspend inline fun quoteReply(message: Message): MessageReceipt<TSubject> = reply(this.message.quote() + message)
suspend inline fun quoteReply(plain: String): MessageReceipt<TSubject> = reply(this.message.quote() + plain)
@JvmName("reply2")
suspend inline fun String.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
@JvmName("reply2")
suspend inline fun Message.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
@JvmName("reply2")
suspend inline fun MessageChain.quoteReply(): MessageReceipt<TSubject> = quoteReply(this)
// endregion
inline operator fun <M : Message> get(at: Message.Key<M>): M {
return this.message[at]
}
inline fun At.isBot(): Boolean = target == bot.id
// endregion
// region 下载图片
/**
* 获取图片下载链接
*
* @return "http://gchat.qpic.cn/gchatpic_new/..."
*/
suspend inline fun Image.url(): String = bot.queryImageUrl(this@url)
/**
* 获取图片下载链接并开始下载.
*
* @see ByteReadChannel.copyAndClose
* @see ByteReadChannel.copyTo
*/
@Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION")
@PlannedRemoval("1.0.0")
@Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING)
suspend inline fun Image.channel(): ByteReadChannel = bot.openChannel(this)
// endregion
@PlannedRemoval("1.0.0")
@Deprecated("use reply(String) for clear semantics", ReplaceWith("reply(this)"),
DeprecationLevel.ERROR)
@JvmName("reply1")
suspend inline fun String.reply(): MessageReceipt<TSubject> = reply(this)
@PlannedRemoval("1.0.0")
@Deprecated("use reply(String) for clear semantics", ReplaceWith("reply(this)"),
level = DeprecationLevel.ERROR)
@JvmName("reply1")
suspend inline fun Message.reply(): MessageReceipt<TSubject> = reply(this)
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@get:JvmSynthetic
@get:JvmName("getSender")
@Suppress("DEPRECATION_ERROR", "INAPPLICABLE_JVM_NAME")
val senderDeprecated: QQ
get() = sender as QQ
@Suppress("DEPRECATION_ERROR")
@PlannedRemoval("1.0.0")
@Deprecated("removed",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("At(this as? Member ?: error(\"`QQ.at` can only be used in GroupMessage\"))",
"net.mamoe.mirai.message.data.At",
"net.mamoe.mirai.contact.Member"))
fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage"))
@PlannedRemoval("1.0.0")
@Deprecated("removed",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("(this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) ?: error(\"`At.member` can only be used in GroupMessage\")"))
fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target)
?: error("`At.member` can only be used in GroupMessage")
}
/**
* 判断两个 [MessagePacket] [MessagePacket.sender] [MessagePacket.subject] 是否相同
*/
@SinceMirai("0.29.0")
fun ContactMessage.isContextIdenticalWith(another: ContactMessage): Boolean {
return this.sender == another.sender && this.subject == another.subject && this.bot == another.bot
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [this] 相同且通过 [筛选][filter] [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值
*
* @see syncFromEvent
*/
@JvmSynthetic
suspend inline fun <reified P : ContactMessage> P.nextMessage(
timeoutMillis: Long = -1,
crossinline filter: suspend P.(P) -> Boolean
): MessageChain {
return syncFromEvent<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) }
}.message
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [this] 相同且通过 [筛选][filter] [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @param filter 过滤器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值
* @return 消息链. 超时时返回 `null`
*
* @see syncFromEventOrNull
*/
@JvmSynthetic
suspend inline fun <reified P : ContactMessage> P.nextMessageOrNull(
timeoutMillis: Long = -1,
crossinline filter: suspend P.(P) -> Boolean
): MessageChain? {
return syncFromEventOrNull<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) }
}?.message
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [this] 相同的 [MessagePacket]
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
*
* @throws TimeoutCancellationException
*
* @see syncFromEvent
*/
@JvmSynthetic
suspend inline fun <reified P : ContactMessage> P.nextMessage(
timeoutMillis: Long = -1
): MessageChain {
return syncFromEvent<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessage) }
}.message
}
/**
* @see nextMessage
* @throws TimeoutCancellationException
*/
@JvmSynthetic
inline fun <reified P : ContactMessage> P.nextMessageAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<MessageChain> {
return this.bot.async(coroutineContext) {
syncFromEvent<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageAsync) }
}.message
}
}
/**
* @see nextMessage
*/
@JvmSynthetic
inline fun <reified P : ContactMessage> P.nextMessageAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext,
crossinline filter: suspend P.(P) -> Boolean
): Deferred<MessageChain> {
return this.bot.async(coroutineContext) {
syncFromEvent<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageAsync) }
.takeIf { filter(this, this) }
}.message
}
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [this] 相同的 [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @return 消息链. 超时时返回 `null`
*
* @see syncFromEventOrNull
*/
@JvmSynthetic
suspend inline fun <reified P : ContactMessage> P.nextMessageOrNull(
timeoutMillis: Long = -1
): MessageChain? {
return syncFromEventOrNull<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }
}?.message
}
/**
* @see nextMessageOrNull
*/
@JvmSynthetic
inline fun <reified P : ContactMessage> P.nextMessageOrNullAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<MessageChain?> {
return this.bot.async(coroutineContext) {
syncFromEventOrNull<P, P>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageOrNullAsync) }
}?.message
}
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [this] 相同的 [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
*
* @see syncFromEvent
* @see whileSelectMessages
* @see selectMessages
*/
@JvmSynthetic
suspend inline fun <reified M : Message> ContactMessage.nextMessageContaining(
timeoutMillis: Long = -1
): M {
return syncFromEvent<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContaining) }
.takeIf { this.message.anyIsInstance<M>() }
}.message.firstIsInstance()
}
@JvmSynthetic
inline fun <reified M : Message> ContactMessage.nextMessageContainingAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<M> {
return this.bot.async(coroutineContext) {
@Suppress("RemoveExplicitTypeArguments")
syncFromEvent<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingAsync) }
.takeIf { this.message.anyIsInstance<M>() }
}.message.firstIsInstance<M>()
}
}
/**
* 挂起当前协程, 等待下一条 [MessagePacket.sender] [MessagePacket.subject] [this] 相同并含有 [M] 类型的消息的 [MessagePacket]
*
* [filter] 抛出了一个异常, 本函数会立即抛出这个异常.
*
* @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制
* @return 指定类型的消息. 超时时返回 `null`
*
* @see syncFromEventOrNull
*/
@JvmSynthetic
suspend inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNull(
timeoutMillis: Long = -1
): M? {
return syncFromEventOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) }
.takeIf { this.message.anyIsInstance<M>() }
}?.message?.firstIsInstance()
}
@JvmSynthetic
inline fun <reified M : Message> ContactMessage.nextMessageContainingOrNullAsync(
timeoutMillis: Long = -1,
coroutineContext: CoroutineContext = EmptyCoroutineContext
): Deferred<M?> {
return this.bot.async(coroutineContext) {
syncFromEventOrNull<ContactMessage, ContactMessage>(timeoutMillis) {
takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNullAsync) }
.takeIf { this.message.anyIsInstance<M>() }
}?.message?.firstIsInstance<M>()
}
}
@PlannedRemoval("1.0.0")
@Suppress("DEPRECATION")
@Deprecated(level = DeprecationLevel.HIDDEN, message = "for binary compatibility")
fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean {
return (this as ContactMessage).isContextIdenticalWith(another as ContactMessage)
}

View File

@ -13,43 +13,33 @@ package net.mamoe.mirai.message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.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)"
}

View File

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

View File

@ -0,0 +1,219 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress(
"EXPERIMENTAL_UNSIGNED_LITERALS",
"EXPERIMENTAL_API_USAGE",
"unused",
"DECLARATION_CANT_BE_INLINED", "UNCHECKED_CAST", "NOTHING_TO_INLINE"
)
@file:OptIn(MiraiInternalAPI::class)
@file:JvmMultifileClass
@file:JvmName("MessageEventKt")
package net.mamoe.mirai.message
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/**
* 一个 (收到的) 消息事件.
*
* 它是一个 [BotEvent], 因此可以被 [监听][Bot.subscribe]
*
* 支持的消息类型:
* - [群消息事件][GroupMessageEvent]
* - [好友消息事件][FriendMessageEvent]
* - [临时会话消息事件][TempMessageEvent]
*
* @see isContextIdenticalWith 判断语境是否相同
*/
@Suppress("DEPRECATION_ERROR")
abstract class MessageEvent : @PlannedRemoval("1.2.0") ContactMessage(),
BotEvent, MessageEventExtensions<User, Contact> {
/**
* 与这个消息事件相关的 [Bot]
*/
abstract override val bot: Bot
/**
* 消息事件主体.
*
* - 对于好友消息, 这个属性为 [Friend] 的实例, [sender] 引用相同;
* - 对于临时会话消息, 这个属性为 [Member] 的实例, [sender] 引用相同;
* - 对于群消息, 这个属性为 [Group] 的实例, [GroupMessageEvent.group] 引用相同
*
* 在回复消息时, 可通过 [subject] 作为回复对象
*/
abstract override val subject: Contact
/**
* 发送人.
*
* 在好友消息时为 [Friend] 的实例, 在群消息时为 [Member] 的实例
*/
abstract override val sender: User
abstract val senderName: String
/** 消息内容 */
abstract override val message: MessageChain
/** 消息发送时间 (由服务器提供) */
abstract val time: Int
/** 消息源 */
open val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming
}
/** 消息事件的扩展函数 */
@Suppress("EXPOSED_SUPER_INTERFACE") // Functions are visible
interface MessageEventExtensions<out TSender : User, out TSubject : Contact> :
MessageEventPlatformExtensions<TSender, TSubject> {
// region 发送 Message
/**
* 给这个消息事件的主体发送消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
@JvmSynthetic
suspend inline fun reply(message: Message): MessageReceipt<TSubject> =
subject.sendMessage(message.asMessageChain()) as MessageReceipt<TSubject>
@JvmSynthetic
suspend inline fun reply(plain: String): MessageReceipt<TSubject> =
subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt<TSubject>
// endregion
@JvmSynthetic
suspend inline fun ExternalImage.upload(): Image = this.upload(subject)
@JvmSynthetic
suspend inline fun ExternalImage.send(): MessageReceipt<TSubject> = this.sendTo(subject)
@JvmSynthetic
suspend inline fun Image.send(): MessageReceipt<TSubject> = this.sendTo(subject)
@JvmSynthetic
suspend inline fun Message.send(): MessageReceipt<TSubject> = this.sendTo(subject)
@JvmSynthetic
suspend inline fun String.send(): MessageReceipt<TSubject> = this.toMessage().sendTo(subject)
// region 引用回复
/**
* 给这个消息事件的主体发送引用回复消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
@JvmSynthetic
suspend inline fun quoteReply(message: MessageChain): MessageReceipt<TSubject> =
reply(this.message.quote() + message)
@JvmSynthetic
suspend inline fun quoteReply(message: Message): MessageReceipt<TSubject> = reply(this.message.quote() + message)
@JvmSynthetic
suspend inline fun quoteReply(plain: String): MessageReceipt<TSubject> = reply(this.message.quote() + plain)
@JvmSynthetic
inline operator fun <M : Message> get(at: Message.Key<M>): M {
return this.message[at]
}
@JvmSynthetic
inline fun At.isBot(): Boolean = target == bot.id
/**
* 获取图片下载链接
* @return "http://gchat.qpic.cn/gchatpic_new/..."
*/
@JvmSynthetic
suspend inline fun Image.url(): String = bot.queryImageUrl(this@url)
}
/** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */
internal expect interface MessageEventPlatformExtensions<out TSender : User, out TSubject : Contact> {
val subject: TSubject
val sender: TSender
val message: MessageChain
val bot: Bot
}
/**
* 已废弃, 请使用 [MessageEvent]
*/
@PlannedRemoval("1.2.0")
@Deprecated(
message = "use MessageEvent",
replaceWith = ReplaceWith("MessageEvent", "net.mamoe.mirai.message.MessageEvent"),
level = DeprecationLevel.ERROR
)
abstract class MessagePacketBase<out TSender : User, out TSubject : Contact> : Packet, BotEvent, AbstractEvent()
@PlannedRemoval("1.2.0")
@Deprecated(
message = "Ambiguous name. Use MessageEvent instead",
replaceWith = ReplaceWith("MessageEvent", "net.mamoe.mirai.message.MessageEvent"),
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
abstract class MessagePacket : MessagePacketBase<User, Contact>(),
BotEvent, MessageEventExtensions<User, Contact>
@PlannedRemoval("1.2.0")
@Deprecated(
message = "Ambiguous name. Use MessageEvent instead",
replaceWith = ReplaceWith("MessageEvent", "net.mamoe.mirai.message.MessageEvent"),
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
abstract class ContactMessage : MessagePacket(),
BotEvent, MessageEventExtensions<User, Contact>
@PlannedRemoval("1.2.0")
@Deprecated(
message = "Ambiguous name. Use FriendMessageEvent instead",
replaceWith = ReplaceWith("FriendMessageEvent", "net.mamoe.mirai.message.FriendMessageEvent"),
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
abstract class FriendMessage : MessageEvent()
@PlannedRemoval("1.2.0")
@Deprecated(
message = "Ambiguous name. Use GroupMessageEvent instead",
replaceWith = ReplaceWith("GroupMessageEvent", "net.mamoe.mirai.message.GroupMessageEvent"),
level = DeprecationLevel.ERROR
)
@Suppress("DEPRECATION_ERROR")
abstract class GroupMessage : MessageEvent()
@PlannedRemoval("1.2.0")
@Deprecated(
message = "Ambiguous name. Use TempMessageEvent instead",
replaceWith = ReplaceWith("TempMessageEvent", "net.mamoe.mirai.message.TempMessageEvent"),
level = DeprecationLevel.ERROR
)
abstract class TempMessage : MessageEvent()

View File

@ -19,7 +19,6 @@ import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recallIn
import net.mamoe.mirai.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)
}
}
/**

View File

@ -1,33 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS")
package net.mamoe.mirai.message
@Suppress("unused")
enum class MessageType(val value: UByte) {
PLAIN_TEXT(0x01u),
AT(0x01u), // same as PLAIN
FACE(0x02u),
/**
* [ImageId.value] 长度为 42 的图片
*/
IMAGE_42(0x03u),
/**
* [ImageId.value] 长度为 37 的图片
*/
IMAGE_37(0x06u),
XML(0x19u)
;
inline val intValue: Int get() = this.value.toInt()
}

View File

@ -1,3 +1,5 @@
@file:Suppress("DEPRECATION_ERROR", "unused", "NOTHING_TO_INLINE")
package net.mamoe.mirai.message
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)"
}

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,6 @@ import net.mamoe.mirai.message.data.PokeMessage.Types
import net.mamoe.mirai.message.data.VipFace.Companion
import net.mamoe.mirai.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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,43 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmName("HummerMessageKt")
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.SinceMirai
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/*
因为文件改名为做的兼容
*/
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("flash")
@SinceMirai("0.33.0")
inline fun Image.flash2(): FlashImage = FlashImage(this)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("flash")
@JvmSynthetic
@SinceMirai("0.33.0")
inline fun GroupImage.flash2(): GroupFlashImage = FlashImage(this) as GroupFlashImage
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("flash")
@JvmSynthetic
@SinceMirai("0.33.0")
inline fun FriendImage.flash2(): FriendFlashImage = FlashImage(this) as FriendFlashImage

View File

@ -1,45 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmName("MessageKt")
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.message.data
import net.mamoe.mirai.utils.PlannedRemoval
import kotlin.jvm.JvmName
/*
因为文件改名为做的兼容
*/
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("isPlain")
inline fun Message.isPlain2(): Boolean = this is PlainText
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("isNotPlain")
inline fun Message.isNotPlain2(): Boolean = this !is PlainText
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
@JvmName("repeat")
// inline: for future removal
inline fun Message.repeat2(count: Int): MessageChain {
if (this is ConstrainSingle<*>) {
// fast-path
return this.asMessageChain()
}
return buildMessageChain(count) {
add(this@repeat2)
}
}

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