Commit 8ca4357e authored by Him188's avatar Him188 Committed by GitHub

Merge pull request #304 from mamoe/1.0.0

1.0.0
parents 01d16253 2d9db234
......@@ -101,7 +101,7 @@
#### `OfflineMessageSource` 构造
可使用 DSL 构造离线消息, 修改其发送人, 发送时间, 发送内容等. 这对于跨群转发等情况十分有用.
[OfflineMessageSource.kt: Line 90](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/OfflineMessageSource.kt#L90)
[MessageSourceBuilder.kt: Line 90](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSourceBuilder.kt#L90)
DSL 总览:
```
val source: OfflineMessageSource = bot.buildMessageSource {
......
......@@ -6,7 +6,7 @@ import kotlin.math.pow
buildscript {
repositories {
mavenLocal()
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
// maven(url = "https://mirrors.huaweicloud.com/repository/maven")
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
jcenter()
google()
......@@ -51,7 +51,7 @@ allprojects {
version = Versions.Mirai.version
repositories {
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
// maven(url = "https://mirrors.huaweicloud.com/repository/maven")
maven(url = "https://dl.bintray.com/kotlin/kotlin-eap")
jcenter()
google()
......
......@@ -24,6 +24,8 @@ object Versions {
const val dokka = "0.10.1"
}
const val jcekt = "1.0.0"
object Android {
const val androidGradlePlugin = "3.5.3"
}
......
......@@ -51,6 +51,7 @@ kotlin {
api(kotlin("stdlib", Versions.Kotlin.stdlib))
api(kotlinx("serialization-runtime-common", Versions.Kotlin.serialization))
api(kotlinx("serialization-protobuf-common", Versions.Kotlin.serialization))
api("moe.him188:jcekt-common:${Versions.jcekt}")
api("org.jetbrains.kotlinx:atomicfu:${Versions.Kotlin.atomicFU}")
api(kotlinx("io", Versions.Kotlin.io))
api(kotlinx("coroutines-io", Versions.Kotlin.coroutinesIo))
......@@ -86,6 +87,7 @@ kotlin {
dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
// api(kotlinx("coroutines-debug", "1.3.5"))
api("moe.him188:jcekt:${Versions.jcekt}")
api(kotlinx("serialization-runtime", Versions.Kotlin.serialization))
//api(kotlinx("serialization-protobuf", Versions.Kotlin.serialization))
......
......@@ -15,7 +15,6 @@ import io.ktor.client.HttpClient
import io.ktor.client.request.*
import io.ktor.client.request.forms.MultiPartFormDataContent
import io.ktor.client.request.forms.formData
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.async
import kotlinx.coroutines.io.ByteReadChannel
......@@ -51,7 +50,6 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.encodeToString
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.utils.toReadPacket
import net.mamoe.mirai.utils.*
import kotlin.collections.asSequence
import kotlin.contracts.ExperimentalContracts
......@@ -190,7 +188,7 @@ internal class QQAndroidBot constructor(
}
}
group.checkBotPermissionOperator()
group.checkBotPermission(MemberPermission.ADMINISTRATOR)
}
override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) {
......@@ -351,10 +349,6 @@ internal abstract class QQAndroidBotBase constructor(
return sequence
}
override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult {
TODO("not implemented")
}
@Suppress("RemoveExplicitTypeArguments") // false positive
override suspend fun recall(source: MessageSource) {
check(source is MessageSourceInternal)
......@@ -375,7 +369,7 @@ internal abstract class QQAndroidBotBase constructor(
else -> error("stub")
}
if (this.id != source.fromId) {
group.checkBotPermissionOperator()
group.checkBotPermission(MemberPermission.ADMINISTRATOR)
}
MessageRecallEvent.GroupRecall(
this,
......@@ -679,8 +673,8 @@ internal abstract class QQAndroidBotBase constructor(
response.proto.uint32UpIp.zip(response.proto.uint32UpPort),
response.proto.msgSig,
MiraiPlatformUtils.md5(body),
body.toReadPacket(),
body.size.toLong().and(0xFFFF_FFFF), // don't use toLongUnsigned: Overload resolution ambiguity
@Suppress("INVISIBLE_REFERENCE")
net.mamoe.mirai.utils.internal.asReusableInput0(body), // don't use toLongUnsigned: Overload resolution ambiguity
"group long message",
27
)
......@@ -766,13 +760,6 @@ internal abstract class QQAndroidBotBase constructor(
}
}
@Suppress("DeprecatedCallableAddReplaceWith")
@PlannedRemoval("1.0.0")
@Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING)
override suspend fun openChannel(image: Image): ByteReadChannel {
return MiraiPlatformUtils.Http.get<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel()
}
/**
* 获取 获取群公告 所需的 bkn 参数
* */
......
......@@ -87,6 +87,10 @@ internal class FriendImpl(
@JvmSynthetic
@OptIn(MiraiInternalAPI::class, ExperimentalStdlibApi::class, ExperimentalTime::class)
override suspend fun uploadImage(image: ExternalImage): Image = try {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
if (image.input is net.mamoe.mirai.utils.internal.DeferredReusableInput) {
image.input.init(bot.configuration.fileCacheStrategy)
}
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
}
......@@ -96,14 +100,15 @@ internal class FriendImpl(
srcUin = bot.id.toInt(),
dstUin = id.toInt(),
fileId = 0,
fileMd5 = image.md5,
fileSize = image.inputSize.toInt(),
fileName = image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName,
fileMd5 = @Suppress("INVISIBLE_MEMBER") image.md5,
fileSize = @Suppress("INVISIBLE_MEMBER")
image.input.size.toInt(),
fileName = @Suppress("INVISIBLE_MEMBER") image.md5.toUHexString("") + "." + ExternalImage.defaultFormatName,
imgOriginal = 1
)
).sendAndExpect<LongConn.OffPicUp.Response>()
@Suppress("UNCHECKED_CAST", "DEPRECATION") // bug
@Suppress("UNCHECKED_CAST", "DEPRECATION", "INVISIBLE_MEMBER")
return when (response) {
is LongConn.OffPicUp.Response.FileExists -> net.mamoe.mirai.message.data.OfflineFriendImage(response.resourceId)
.also {
......@@ -111,7 +116,7 @@ internal class FriendImpl(
}
is LongConn.OffPicUp.Response.RequireUpload -> {
bot.network.logger.verbose {
"[Http] Uploading friend image, size=${image.inputSize.sizeToString()}"
"[Http] Uploading friend image, size=${image.input.size.sizeToString()}"
}
val time = measureTime {
......@@ -120,13 +125,12 @@ internal class FriendImpl(
bot.id,
null,
imageInput = image.input,
inputSize = image.inputSize,
uKeyHex = response.uKey.toUHexString("")
)
}
bot.network.logger.verbose {
"[Http] Uploading friend image: succeed at ${(image.inputSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
"[Http] Uploading friend image: succeed at ${(image.input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
}
/*
......@@ -151,6 +155,7 @@ internal class FriendImpl(
}
}
} finally {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
(image.input as? Closeable)?.close()
}
}
\ No newline at end of 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
)
......
......@@ -105,7 +105,7 @@ internal class MemberImpl constructor(
get() = _nameCard
set(newValue) {
if (id != bot.id) {
group.checkBotPermissionOperator()
group.checkBotPermission(MemberPermission.ADMINISTRATOR)
}
if (_nameCard != newValue) {
val oldValue = _nameCard
......@@ -118,7 +118,7 @@ internal class MemberImpl constructor(
newValue
).sendWithoutExpect()
}
MemberCardChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast()
MemberCardChangeEvent(oldValue, newValue, this@MemberImpl).broadcast()
}
}
}
......@@ -205,6 +205,7 @@ internal class MemberImpl constructor(
check(response.success) { "kick failed: ${response.ret}" }
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
group.members.delegate.removeIf { it.id == this@MemberImpl.id }
MemberLeaveEvent.Kick(this@MemberImpl, null).broadcast()
}
......
......@@ -59,17 +59,17 @@ internal fun Contact.logMessageSent(message: Message) {
}
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal fun ContactMessage.logMessageReceived() {
internal fun MessageEvent.logMessageReceived() {
when (this) {
is GroupMessage -> bot.logger.verbose {
is GroupMessageEvent -> bot.logger.verbose {
"[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(${sender.id}) -> ${message.toString()
.singleLine()}"
}
is TempMessage -> bot.logger.verbose {
is TempMessageEvent -> bot.logger.verbose {
"[${group.name.singleLine()}(${group.id})] ${senderName.singleLine()}(Temp ${sender.id}) -> ${message.toString()
.singleLine()}"
}
is FriendMessage -> bot.logger.verbose {
is FriendMessageEvent -> bot.logger.verbose {
"${sender.nick.singleLine()}(${sender.id}) -> ${message.toString().singleLine()}"
}
}
......
......@@ -358,10 +358,10 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
="" a_actionData="" url=""/></msg>
*/
/**
* [JsonMessage]
* json?
*/
1 -> @Suppress("DEPRECATION_ERROR")
list.add(JsonMessage(content))
list.add(ServiceMessage(1, content))
/**
* [LongMessage], [ForwardMessage]
*/
......@@ -381,7 +381,7 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(groupIdOrZero: Long, bot: B
else -> {
if (element.richMsg.serviceId == 60 || content.startsWith("<?")) {
@Suppress("DEPRECATION_ERROR") // bin comp
list.add(XmlMessage(element.richMsg.serviceId, content))
list.add(ServiceMessage(element.richMsg.serviceId, content))
} else list.add(ServiceMessage(element.richMsg.serviceId, content))
}
}
......
......@@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicRef
......@@ -20,7 +22,7 @@ import kotlinx.io.core.use
import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.message.ContactMessage
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.UnsupportedSMSLoginException
import net.mamoe.mirai.network.WrongPasswordException
......@@ -44,6 +46,7 @@ import net.mamoe.mirai.qqandroid.utils.io.useBytes
import net.mamoe.mirai.qqandroid.utils.retryCatching
import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext
import kotlin.jvm.JvmField
import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime
......@@ -204,6 +207,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
private val _pendingEnabled = atomic(true)
internal val pendingEnabled get() = _pendingEnabled.value
@JvmField
@Volatile
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? =
LockFreeLinkedList()
......@@ -491,7 +495,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
is Packet.NoLog -> {
// nothing to do
}
is ContactMessage -> packet.logMessageReceived()
is MessageEvent -> packet.logMessageReceived()
is Event -> bot.logger.verbose { "Event: ${packet.toString().singleLine()}" }
else -> logger.verbose { "Packet: ${packet.toString().singleLine()}" }
}
......
......@@ -77,6 +77,10 @@ internal open class QQAndroidClient(
val device: DeviceInfo = SystemDeviceInfo(context),
bot: QQAndroidBot
) {
@Suppress("INVISIBLE_MEMBER")
val subAppId: Long
get() = bot.configuration.protocol.id
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
val keys: Map<String, ByteArray> by lazy {
......@@ -368,7 +372,7 @@ internal class WLoginSigInfo(
val deviceToken: ByteArray
) {
override fun toString(): String {
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=${psKeyMap.toString()}, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
}
}
......
......@@ -7,6 +7,8 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
package net.mamoe.mirai.qqandroid.network.highway
import io.ktor.client.HttpClient
......@@ -20,22 +22,24 @@ import io.ktor.utils.io.ByteWriteChannel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.io.InputStream
import kotlinx.io.core.Input
import kotlinx.io.core.discardExact
import kotlinx.io.core.readAvailable
import kotlinx.io.core.use
import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.utils.*
import net.mamoe.mirai.qqandroid.utils.PlatformSocket
import net.mamoe.mirai.qqandroid.utils.SocketException
import net.mamoe.mirai.qqandroid.utils.addSuppressedMirai
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.withUse
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.internal.ReusableInput
import net.mamoe.mirai.utils.verbose
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.math.roundToInt
import kotlin.time.ExperimentalTime
......@@ -47,8 +51,7 @@ internal suspend fun HttpClient.postImage(
htcmd: String,
uin: Long,
groupcode: Long?,
imageInput: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor
inputSize: Long,
imageInput: ReusableInput,
uKeyHex: String
): Boolean = post<HttpStatusCode> {
url {
......@@ -63,7 +66,7 @@ internal suspend fun HttpClient.postImage(
parameters["term"] = "pc"
parameters["ver"] = "5603"
parameters["filesize"] = inputSize.toString()
parameters["filesize"] = imageInput.size.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
......@@ -72,63 +75,46 @@ internal suspend fun HttpClient.postImage(
body = object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize
override val contentLength: Long = imageInput.size
@OptIn(MiraiExperimentalAPI::class)
override suspend fun writeTo(channel: ByteWriteChannel) {
ByteArrayPool.useInstance { buffer: ByteArray ->
when (imageInput) {
is Input -> {
var size: Int
while (imageInput.readAvailable(buffer).also { size = it } > 0) {
channel.writeFully(buffer, 0, size)
channel.flush()
}
}
is ByteReadChannel -> imageInput.copyAndClose(channel)
is InputStream -> {
var size: Int
while (imageInput.read(buffer).also { size = it } > 0) {
channel.writeFully(buffer, 0, size)
channel.flush()
}
}
else -> error("unsupported imageInput: ${imageInput::class.simpleName}")
}
}
imageInput.writeTo(channel)
}
}
} == HttpStatusCode.OK
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal object HighwayHelper {
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
suspend fun uploadImageToServers(
bot: QQAndroidBot,
servers: List<Pair<Int, Int>>,
uKey: ByteArray,
image: ExternalImage,
image: ReusableInput,
kind: String,
commandId: Int
) = uploadImageToServers(bot, servers, uKey, image.md5, image.input, image.inputSize, kind, commandId)
) = uploadImageToServers(bot, servers, uKey, image.md5, image, kind, commandId)
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@OptIn(ExperimentalTime::class)
suspend fun uploadImageToServers(
bot: QQAndroidBot,
servers: List<Pair<Int, Int>>,
uKey: ByteArray,
md5: ByteArray,
input: Any,
inputSize: Long,
input: ReusableInput,
kind: String,
commandId: Int
) = servers.retryWithServers(
(inputSize * 1000 / 1024 / 10).coerceAtLeast(5000),
(input.size * 1000 / 1024 / 10).coerceAtLeast(5000),
onFail = {
throw IllegalStateException("cannot upload $kind, failed on all servers.", it)
}
) { ip, port ->
bot.network.logger.verbose {
"[Highway] Uploading $kind to ${ip}:$port, size=${inputSize.sizeToString()}"
"[Highway] Uploading $kind to ${ip}:$port, size=${input.size.sizeToString()}"
}
val time = measureTime {
......@@ -137,7 +123,6 @@ internal object HighwayHelper {
serverIp = ip,
serverPort = port,
imageInput = input,
inputSize = inputSize.toInt(),
fileMd5 = md5,
ticket = uKey,
commandId = commandId
......@@ -145,22 +130,21 @@ internal object HighwayHelper {
}
bot.network.logger.verbose {
"[Highway] Uploading $kind: succeed at ${(inputSize.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
"[Highway] Uploading $kind: succeed at ${(input.size.toDouble() / 1024 / time.inSeconds).roundToInt()} KiB/s"
}
}
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER")
@OptIn(InternalCoroutinesApi::class)
internal suspend fun uploadImage(
client: QQAndroidClient,
serverIp: String,
serverPort: Int,
ticket: ByteArray,
imageInput: Any,
inputSize: Int,
imageInput: ReusableInput,
fileMd5: ByteArray,
commandId: Int // group=2, friend=1
) {
require(imageInput is Input || imageInput is InputStream || imageInput is ByteReadChannel) { "unsupported imageInput: ${imageInput::class.simpleName}" }
require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" }
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
// require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
......@@ -177,22 +161,24 @@ internal object HighwayHelper {
socket.use {
createImageDataPacketSequence(
client = client,
appId = client.subAppId.toInt(),
command = "PicUp.DataUp",
commandId = commandId,
ticket = ticket,
data = imageInput,
dataSize = inputSize,
fileMd5 = fileMd5
).collect {
socket.send(it)
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
socket.read().withUse {
discardExact(1)
val headLength = readInt()
discardExact(4)
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
).withUse {
flow.collect {
socket.send(it)
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
socket.read().withUse {
discardExact(1)
val headLength = readInt()
discardExact(4)
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" }
}
}
}
}
......
......@@ -11,12 +11,7 @@
package net.mamoe.mirai.qqandroid.network.highway
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import kotlinx.serialization.InternalSerializationApi
......@@ -25,41 +20,34 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.utils.ByteArrayPool
import net.mamoe.mirai.qqandroid.utils.MiraiPlatformUtils
import net.mamoe.mirai.qqandroid.utils.io.chunkedFlow
import net.mamoe.mirai.qqandroid.utils.io.serialization.toByteArray
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.internal.ChunkedFlowSession
import net.mamoe.mirai.utils.internal.ChunkedInput
import net.mamoe.mirai.utils.internal.ReusableInput
import net.mamoe.mirai.utils.internal.map
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal fun createImageDataPacketSequence(
// RequestDataTrans
client: QQAndroidClient,
command: String,
appId: Int = 537062845,
appId: Int,
dataFlag: Int = 4096,
commandId: Int,
localId: Int = 2052,
ticket: ByteArray,
data: Any,
dataSize: Int,
data: ReusableInput,
fileMd5: ByteArray,
sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE
): Flow<ByteReadPacket> {
): ChunkedFlowSession<ByteReadPacket> {
ByteArrayPool.checkBufferSize(sizePerPacket)
require(data is Input || data is InputStream || data is ByteReadChannel) { "unsupported data: ${data::class.simpleName}" }
// require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" }
require(data !is ByteReadPacket || data.remaining.toInt() == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as ByteReadPacket).remaining}" }
val flow = when (data) {
is ByteReadPacket -> data.chunkedFlow(sizePerPacket)
is Input -> data.chunkedFlow(sizePerPacket)
is ByteReadChannel -> data.chunkedFlow(sizePerPacket)
is InputStream -> data.chunkedFlow(sizePerPacket)
else -> error("unreachable code")
}
val session: ChunkedFlowSession<ChunkedInput> = data.chunkedFlow(sizePerPacket)
var offset = 0L
return flow.map { chunkedInput ->
return session.map { chunkedInput ->
buildPacket {
val head = CSDataHighwayHead.ReqDataHighwayHead(
msgBasehead = CSDataHighwayHead.DataHighwayHead(
......@@ -82,7 +70,7 @@ internal fun createImageDataPacketSequence(
// cacheAddr = 812157193,
datalength = chunkedInput.bufferSize,
dataoffset = offset,
filesize = dataSize.toLong(),
filesize = data.size,
serviceticket = ticket,
md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
fileMd5 = fileMd5,
......
......@@ -10,9 +10,9 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Serializable
......
......@@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Serializable
......
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
......
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Serializable
......
......@@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
internal class OnlinePushPack {
......
......@@ -10,10 +10,10 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Suppress("ArrayInDataClass")
......
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Serializable
......
......@@ -10,9 +10,9 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
private val EMPTY_MAP = mapOf<String, String>()
......
......@@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Serializable
......
......@@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Serializable
......
......@@ -10,8 +10,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.Serializable
import moe.him188.jcekt.JceId
import net.mamoe.mirai.qqandroid.utils.io.JceStruct
import net.mamoe.mirai.qqandroid.utils.io.serialization.jce.JceId
import kotlin.jvm.JvmField
@Serializable
......
......@@ -197,6 +197,7 @@ internal object KnownPacketFactories {
readString(readInt() - 4)// uinAccount
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { data ->
val size = this.readAvailable(data)
......@@ -219,6 +220,7 @@ internal object KnownPacketFactories {
it as IncomingPacket<T>
if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.network.pendingIncomingPackets?.addLast(it.also {
it.consumer = consumer
it.flag2 = flag2
......@@ -384,6 +386,7 @@ internal object KnownPacketFactories {
}
0 -> {
val data = if (bot.client.loginState == 0) {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { byteArrayBuffer ->
val size = (this.remaining - 1).toInt()
this.readFully(byteArrayBuffer, 0, size)
......
......@@ -83,7 +83,7 @@ internal fun BytePacketBuilder.t18(
@OptIn(MiraiInternalAPI::class)
internal fun BytePacketBuilder.t106(
appId: Long = 16L,
subAppId: Long = 537062845L,
subAppId: Long,
appClientVersion: Int = 0,
uin: Long,
n5_always_1: Int = 1,
......@@ -159,7 +159,7 @@ internal fun BytePacketBuilder.t116(
internal fun BytePacketBuilder.t100(
appId: Long = 16,
subAppId: Long = 537062845,
subAppId: Long,
appClientVersion: Int
) {
writeShort(0x100)
......
......@@ -100,7 +100,7 @@ internal class TroopManagement {
serviceType = 7,
result = 0,
bodybuffer = Oidb0x88d.ReqBody(
appid = 537062845,
appid = client.subAppId.toInt(),
stzreqgroupinfo = listOf(
Oidb0x88d.ReqGroupInfo(
stgroupinfo = Oidb0x88d.GroupInfo(
......
......@@ -17,6 +17,7 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Cmd0x388
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory
import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.toLongUnsigned
import net.mamoe.mirai.qqandroid.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeProtoBuf
import kotlin.random.Random
......@@ -27,9 +28,6 @@ internal fun getRandomString(length: Int): String =
private val defaultRanges: Array<CharRange> = arrayOf('a'..'z', 'A'..'Z', '0'..'9')
internal fun getRandomString(length: Int, charRange: CharRange): String =
String(CharArray(length) { charRange.random() })
internal fun getRandomString(length: Int, vararg charRanges: CharRange): String =
String(CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() })
......@@ -41,7 +39,7 @@ internal class ImgStore {
uin: Long,
groupCode: Long,
md5: ByteArray,
size: Long,
size: Int,
picWidth: Int = 0, // not orthodox
picHeight: Int = 0, // not orthodox
picType: Int = 1000,
......@@ -63,7 +61,7 @@ internal class ImgStore {
groupCode = groupCode,
srcUin = uin,
fileMd5 = md5,
fileSize = size,
fileSize = size.toLongUnsigned(),
fileId = fileId,
fileName = filename,
picWidth = picWidth,
......
......@@ -30,8 +30,8 @@ import net.mamoe.mirai.event.events.BotJoinGroupEvent
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.TempMessage
import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.TempMessageEvent
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.PttMessage
import net.mamoe.mirai.message.data.Voice
......@@ -228,6 +228,7 @@ internal class MessageSvc {
// 新群
val newGroup = msg.getNewGroup(bot) ?: return@mapNotNull null
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.groups.delegate.addLast(newGroup)
return@mapNotNull BotJoinGroupEvent(newGroup)
} else {
......@@ -237,6 +238,7 @@ internal class MessageSvc {
return@mapNotNull null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
}
......@@ -250,6 +252,7 @@ internal class MessageSvc {
if (group.members.contains(msg.msgHead.authUin)) {
return@mapNotNull null
}
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
.also { group.members.delegate.addLast(it) })
}
......@@ -276,7 +279,7 @@ internal class MessageSvc {
friend.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
if (friend.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull FriendMessage(
return@mapNotNull FriendMessageEvent(
friend,
msg.toMessageChain(bot, groupIdOrZero = 0, onlineSource = true),
msg.msgHead.msgTime
......@@ -307,7 +310,7 @@ internal class MessageSvc {
member.lastMessageSequence.loop { instant ->
if (msg.msgHead.msgSeq > instant) {
if (member.lastMessageSequence.compareAndSet(instant, msg.msgHead.msgSeq)) {
return@mapNotNull TempMessage(
return@mapNotNull TempMessageEvent(
member,
msg.toMessageChain(
bot,
......
......@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive
......@@ -23,7 +23,7 @@ import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.getFriendOrNull
import net.mamoe.mirai.getGroupOrNull
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.contact.*
import net.mamoe.mirai.qqandroid.message.contextualBugReportException
......@@ -87,7 +87,8 @@ internal class OnlinePush {
}
}
val group = bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val group =
bot.getGroupOrNull(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) as GroupImpl? ?: return null // 机器人还正在进群
val sender = if (anonymous != null) {
group.newAnonymous(anonymous.anonNick.encodeToString())
} else {
......@@ -108,12 +109,12 @@ internal class OnlinePush {
}
val flags = extraInfo?.flags ?: 0
return GroupMessage(
return GroupMessageEvent(
senderName = name.also {
if (it != sender.nameCard) {
val origin = sender._nameCard
sender._nameCard = name
MemberCardChangeEvent(origin, name, sender, sender).broadcast() // 不知道operator
MemberCardChangeEvent(origin, name, sender).broadcast()
}
},
sender = sender,
......@@ -562,7 +563,7 @@ internal class OnlinePush {
if (new == old) return@mapNotNull null
member._nameCard = new
return@mapNotNull MemberCardChangeEvent(old, new, member, null)
return@mapNotNull MemberCardChangeEvent(old, new, member)
}
2 -> {
if (info.value.singleOrNull()?.toInt() != 0) {
......
......@@ -24,11 +24,8 @@ import net.mamoe.mirai.qqandroid.utils.io.serialization.jceRequestSBuffer
import net.mamoe.mirai.qqandroid.utils.io.serialization.readUniPacket
import net.mamoe.mirai.qqandroid.utils.io.serialization.writeJceStruct
import net.mamoe.mirai.qqandroid.utils.toByteArray
import net.mamoe.mirai.utils.SinceMirai
internal class ProfileService {
@SinceMirai("0.37.0")
object GroupMngReq : OutgoingPacketFactory<GroupMngReq.GroupMngReqResponse>("ProfileService.GroupMngReq") {
data class GroupMngReqResponse(val errorCode: Int, val errorMessage: String) : Packet
......
......@@ -70,6 +70,7 @@ internal class ConfigPushSvc {
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse? {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { buffer ->
val length = this.readAvailable(buffer)
......
......@@ -25,7 +25,7 @@ internal class Heartbeat {
operator fun invoke(
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, 0, key = NO_ENCRYPT) {
writeSsoPacket(client, 537062845, commandName, sequenceId = it) {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = it) {
}
}
......
......@@ -10,6 +10,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.login
import kotlinx.io.core.ByteReadPacket
import kotlinx.serialization.protobuf.ProtoBuf
import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.network.Packet
......@@ -88,8 +89,6 @@ internal class StatSvc {
override fun toString(): String = "Response(StatSvc.register)"
}
private const val subAppId = 537062845L
@OptIn(MiraiInternalAPI::class)
operator fun invoke(
client: QQAndroidClient,
......@@ -101,7 +100,7 @@ internal class StatSvc {
key = client.wLoginSigInfo.d2Key
) { sequenceId ->
writeSsoPacket(
client, subAppId = subAppId, commandName = commandName,
client, subAppId = client.subAppId, commandName = commandName,
extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId
) {
writeJceStruct(
......@@ -153,7 +152,7 @@ internal class StatSvc {
var44.strVendorName = ROMUtil.getRomName();
var44.strVendorOSName = ROMUtil.getRomVersion(20);
*/
bytes_0x769_reqbody = ProtoBufWithNullableSupport.dump(
bytes_0x769_reqbody = ProtoBuf.dump(
Oidb0x769.RequestBody.serializer(), Oidb0x769.RequestBody(
rpt_config_list = listOf(
Oidb0x769.ConfigSeq(
......
......@@ -32,8 +32,6 @@ internal class WtLogin {
@Suppress("FunctionName")
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal object Login : OutgoingPacketFactory<Login.LoginPacketResponse>("wtlogin.login") {
private const val subAppId = 537062845L
/**
* 提交验证码
*/
......@@ -42,7 +40,7 @@ internal class WtLogin {
client: QQAndroidClient,
ticket: String
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand
writeShort(4) // count of TLVs
......@@ -59,7 +57,7 @@ internal class WtLogin {
captchaSign: ByteArray,
captchaAnswer: String
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(2) // subCommand
writeShort(4) // count of TLVs
......@@ -78,7 +76,7 @@ internal class WtLogin {
client: QQAndroidClient,
t402: ByteArray
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(20) // subCommand
writeShort(4) // count of TLVs, probably ignored by server?
......@@ -100,7 +98,7 @@ internal class WtLogin {
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(
client,
subAppId,
client.subAppId,
commandName,
sequenceId = sequenceId,
unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00"
......@@ -126,13 +124,12 @@ internal class WtLogin {
*/
object SubCommand9 {
private const val appId = 16L
private const val subAppId = 537062845L
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
operator fun invoke(
client: QQAndroidClient
): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId ->
writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(9) // subCommand
writeShort(17) // count of TLVs, probably ignored by server?
......@@ -142,7 +139,7 @@ internal class WtLogin {
t1(client.uin, client.device.ipAddress)
t106(
appId,
subAppId /* maybe 1*/,
client.subAppId /* maybe 1*/,
client.appClientVersion,
client.uin,
1,
......@@ -166,7 +163,7 @@ internal class WtLogin {
if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L)
*/
t116(client.miscBitMap, client.subSigMap)
t100(appId, subAppId, client.appClientVersion)
t100(appId, client.subAppId, client.appClientVersion)
t107(0)
// t108(byteArrayOf())
......
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)
}
}
}
\ No newline at end of file
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
internal typealias ByteArrayPool = net.mamoe.mirai.utils.internal.ByteArrayPool
\ No newline at end of file
......@@ -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
......
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
}
......@@ -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
......
# io.serialization
**序列化支持**
包含:
- QQ 的 JceStruct 相关的全自动序列化和反序列化: [Jce.kt](jce/JceNew.kt)
- Protocol Buffers 的 optional 支持: [ProtoBufWithNullableSupport.kt](ProtoBufWithNullableSupport.kt)
其中, `ProtoBufWithNullableSupport` 的绝大部分源码来自 `kotlinx.serialization`. 原著权归该项目作者所有.
Mirai 所做的修改已经标记上了 `MIRAI MODIFY START`
\ No newline at end of file
/*
* 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()
}
}
/*
* 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
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid.utils.io.serialization.jce
import kotlinx.io.core.Output
import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerialInfo
/**
* 标注 JCE 序列化时使用的 ID
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
internal annotation class JceId(val id: Int)
/**
* 类中元素的 tag
*
* 保留这个结构, 为将来增加功能的兼容性.
*/
@PublishedApi
internal abstract class JceTag {
abstract val id: Int
internal var isSimpleByteArray: Boolean = false
}
internal object JceTagListElement : JceTag() {
override val id: Int get() = 0
override fun toString(): String {
return "JceTagListElement"
}
}
internal object JceTagMapEntryKey : JceTag() {
override val id: Int get() = 0
override fun toString(): String {
return "JceTagMapEntryKey"
}
}
internal object JceTagMapEntryValue : JceTag() {
override val id: Int get() = 1
override fun toString(): String {
return "JceTagMapEntryValue"
}
}
internal data class JceTagCommon(
override val id: Int
) : JceTag()
internal fun JceHead.checkType(type: Byte, message: String, tag: JceTag, descriptor: SerialDescriptor) {
check(this.type == type) {
"type mismatch. " +
"Expected ${JceHead.findJceTypeName(type)}, " +
"actual ${JceHead.findJceTypeName(this.type)} for $message. " +
"Tag info: " +
"id=${tag.id}, " +
"name=${descriptor.getElementName(tag.id)} " +
"in ${descriptor.serialName}" }
}
@PublishedApi
internal fun Output.writeJceHead(type: Byte, tag: Int) {
if (tag < 15) {
writeByte(((tag shl 4) or type.toInt()).toByte())
return
}
if (tag < 256) {
writeByte((type.toInt() or 0xF0).toByte())
writeByte(tag.toByte())
return
}
error("tag is too large: $tag")
}
@OptIn(ExperimentalUnsignedTypes::class)
inline class JceHead(private val value: Long) {
constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong())
val tag: Int get() = (value ushr 32).toInt()
val type: Byte get() = value.toUInt().toByte()
override fun toString(): String {
return "JceHead(tag=$tag, type=$type(${findJceTypeName(type)}))"
}
companion object {
fun findJceTypeName(type: Byte): String {
return when (type) {
Jce.BYTE -> "Byte"
Jce.DOUBLE -> "Double"
Jce.FLOAT -> "Float"
Jce.INT -> "Int"
Jce.LIST -> "List"
Jce.LONG -> "Long"
Jce.MAP -> "Map"
Jce.SHORT -> "Short"
Jce.SIMPLE_LIST -> "SimpleList"
Jce.STRING1 -> "String1"
Jce.STRING4 -> "String4"
Jce.STRUCT_BEGIN -> "StructBegin"
Jce.STRUCT_END -> "StructEnd"
Jce.ZERO_TYPE -> "Zero"
else -> error("illegal jce type: $type")
}
}
}
}
\ No newline at end of file
......@@ -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)
......
......@@ -42,7 +42,6 @@ actual abstract class Group : Contact(), CoroutineScope {
/**
* 群设置
*/
@SinceMirai("0.30.0")
actual abstract val settings: GroupSettings
/**
......
......@@ -15,10 +15,8 @@ package net.mamoe.mirai
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent
import net.mamoe.mirai.event.events.MemberJoinRequestEvent
import net.mamoe.mirai.event.events.NewFriendRequestEvent
......@@ -29,7 +27,6 @@ import net.mamoe.mirai.network.LoginFailedException
import net.mamoe.mirai.utils.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmName
import kotlin.jvm.JvmStatic
import kotlin.jvm.JvmSynthetic
......@@ -48,6 +45,8 @@ suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() }
*
* @see Contact 联系人
* @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close])
*
* @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式.
*/
@Suppress("INAPPLICABLE_JVM_NAME")
@OptIn(MiraiInternalAPI::class, LowLevelAPI::class)
......@@ -56,6 +55,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@PlannedRemoval("1.2.0")
@Deprecated("use botInstances instead", replaceWith = ReplaceWith("botInstances"))
@JvmStatic
val instances: List<WeakRef<Bot>>
......@@ -64,7 +64,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/**
* 复制一份此时的 [Bot] 实例列表.
*/
@SinceMirai("0.39.1")
@JvmStatic
val botInstances: List<Bot>
get() = BotImpl.instances.asSequence().mapNotNull { it.get() }.toList()
......@@ -72,7 +71,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/**
* 遍历每一个 [Bot] 实例
*/
inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block)
/**
* 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException]
......@@ -89,20 +88,14 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
*/
abstract val context: Context
@PlannedRemoval("1.0.0")
@Deprecated("use id instead", replaceWith = ReplaceWith("id"))
abstract val uin: Long
/**
* QQ 号码. 实际类型为 uint
*/
@SinceMirai("0.32.0")
abstract override val id: Long
/**
* 昵称
*/
@SinceMirai("0.33.1")
abstract val nick: String
/**
......@@ -113,7 +106,7 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
// region contacts
/**
* [QQ.id] 与 [Bot.uin] 相同的 [_lowLevelNewFriend] 实例
* [User.id] 与 [Bot.id] 相同的 [_lowLevelNewFriend] 实例
*/
abstract val selfQQ: Friend
......@@ -179,6 +172,8 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
/**
* 获取图片下载链接
*
* @see Image.queryUrl [Image] 的扩展函数
*/
@JvmSynthetic
abstract suspend fun queryImageUrl(image: Image): String
......@@ -193,7 +188,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param targetUin 为用户时为 [Friend.id], 为群时需使用 [Group.calculateGroupUinByGroupCode] 计算
*/
@MiraiExperimentalAPI
@SinceMirai("0.39.0")
abstract fun constructMessageSource(
kind: OfflineMessageSource.Kind,
fromUin: Long, targetUin: Long,
......@@ -201,35 +195,12 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
originalMessage: MessageChain
): OfflineMessageSource
/**
* 获取图片下载链接并开始下载.
*
* @see ByteReadChannel.copyAndClose
* @see ByteReadChannel.copyTo
*/
@PlannedRemoval("1.0.0")
@Deprecated("use your own Http clients, this is going to be removed in 1.0.0", level = DeprecationLevel.WARNING)
@MiraiExperimentalAPI
@JvmSynthetic
abstract suspend fun openChannel(image: Image): ByteReadChannel
/**
* 添加一个好友
*
* @param message 若需要验证请求时的验证消息.
* @param remark 好友备注
*/
@JvmSynthetic
@MiraiExperimentalAPI("未支持")
abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult
/**
* 通过好友验证
*
* @param event 好友验证的事件对象
*/
@SinceMirai("0.35.0")
@JvmSynthetic
abstract suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent)
......@@ -239,7 +210,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 好友验证的事件对象
* @param blackList 拒绝后是否拉入黑名单
*/
@SinceMirai("0.35.0")
@JvmSynthetic
abstract suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false)
......@@ -248,7 +218,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
*
* @param event 加群验证的事件对象
*/
@SinceMirai("0.35.0")
@JvmSynthetic
abstract suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent)
......@@ -258,7 +227,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 加群验证的事件对象
* @param blackList 拒绝后是否拉入黑名单
*/
@SinceMirai("0.35.0")
@JvmSynthetic
abstract suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
......@@ -268,7 +236,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 加群验证的事件对象
* @param blackList 忽略后是否拉入黑名单
*/
@SinceMirai("0.35.0")
@JvmSynthetic
abstract suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false)
......@@ -277,7 +244,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
*
* @param event 邀请入群的事件对象
*/
@SinceMirai("0.39.4")
@JvmSynthetic
abstract suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
......@@ -287,7 +253,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
* @param event 邀请入群的事件对象
*/
@JvmSynthetic
@SinceMirai("0.39.4")
abstract suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent)
// endregion
......@@ -313,19 +278,6 @@ abstract class Bot : CoroutineScope, LowLevelBotAPIAccessor, BotJavaFriendlyAPI(
*/
@MiraiInternalAPI
abstract val network: BotNetworkHandler
@PlannedRemoval("1.0.0.")
@get:JvmName("getSelfQQ")
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
val selfQQDeprecated: QQ
get() = selfQQ
@PlannedRemoval("1.0.0.")
@JvmName("getFriend")
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun getFriendDeprecated(id: Long): QQ = this.getFriend(id)
}
/**
......
......@@ -14,15 +14,16 @@ package net.mamoe.mirai
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/**
* 构造 [Bot] 的工厂.
* 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式.
*
* 在协议模块中有各自的实现.
* - `mirai-core-timpc`: `TIMPC`
* - `mirai-core-qqandroid`: `QQAndroid`
* `mirai-core-qqandroid`: `QQAndroid`
*
* 在 JVM, 请查看 `BotFactoryJvm`
*/
interface BotFactory {
expect interface BotFactory {
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
......@@ -49,6 +50,7 @@ interface BotFactory {
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
@JvmSynthetic
inline fun BotFactory.Bot(
context: Context,
qq: Long,
......@@ -59,6 +61,7 @@ inline fun BotFactory.Bot(
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
@JvmSynthetic
inline fun BotFactory.Bot(
context: Context,
qq: Long,
......
......@@ -24,6 +24,8 @@ import net.mamoe.mirai.network.closeAndJoin
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.internal.retryCatching
import kotlin.coroutines.CoroutineContext
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
/*
* 泛型 N 不需要向外(接口)暴露.
......@@ -46,10 +48,6 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
override val context: Context by context.unsafeWeakRef()
@Deprecated("use id instead", replaceWith = ReplaceWith("id"))
override val uin: Long
get() = this.id
final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) }
init {
......@@ -60,7 +58,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@PublishedApi
internal val instances: LockFreeLinkedList<WeakRef<Bot>> = LockFreeLinkedList()
inline fun forEachInstance(block: (Bot) -> Unit) = instances.forEach {
fun forEachInstance(block: (Bot) -> Unit) = instances.forEach {
it.get()?.let(block)
}
......@@ -90,6 +88,7 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
@Throws(LoginFailedException::class) // only
protected abstract suspend fun relogin(cause: Throwable?)
@OptIn(ExperimentalTime::class)
@Suppress("unused")
private val offlineListener: Listener<BotOfflineEvent> =
this@BotImpl.subscribeAlways(concurrency = Listener.ConcurrencyKind.LOCKED) { event ->
......@@ -107,37 +106,39 @@ abstract class BotImpl<N : BotNetworkHandler> constructor(
}
bot.logger.info { "Connection dropped by server or lost, retrying login" }
tailrec suspend fun reconnect() {
retryCatching<Unit>(configuration.reconnectionRetryTimes,
except = LoginFailedException::class) { tryCount, _ ->
if (tryCount != 0) {
delay(configuration.reconnectPeriodMillis)
}
network.withConnectionLock {
/**
* [BotImpl.relogin] only, no [BotNetworkHandler.init]
*/
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause)
}
logger.info { "Reconnected successfully" }
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
return
}.getOrElse {
if (it is LoginFailedException && !it.killBot) {
val time = measureTime {
tailrec suspend fun reconnect() {
retryCatching<Unit>(configuration.reconnectionRetryTimes,
except = LoginFailedException::class) { tryCount, _ ->
if (tryCount != 0) {
delay(configuration.reconnectPeriodMillis)
}
network.withConnectionLock {
/**
* [BotImpl.relogin] only, no [BotNetworkHandler.init]
*/
@OptIn(ThisApiMustBeUsedInWithConnectionLockBlock::class)
relogin((event as? BotOfflineEvent.Dropped)?.cause)
}
BotReloginEvent(bot, (event as? BotOfflineEvent.Dropped)?.cause).broadcast()
return
}.getOrElse {
if (it is LoginFailedException && !it.killBot) {
logger.info { "Cannot reconnect" }
logger.warning(it)
logger.info { "Retrying in 3s..." }
delay(3000)
return@getOrElse
}
logger.info { "Cannot reconnect" }
logger.warning(it)
logger.info { "Retrying in 3s..." }
delay(3000)
return@getOrElse
throw it
}
logger.info { "Cannot reconnect" }
throw it
reconnect()
}
reconnect()
}
reconnect()
logger.info { "Reconnected successfully in ${time.inMilliseconds} ms" }
}
is BotOfflineEvent.Active -> {
val msg = if (event.cause == null) {
......
......@@ -32,11 +32,11 @@ import kotlin.jvm.JvmSynthetic
/**
* 联系人. 虽然叫做联系人, 但他的子类有 [用户][User], 和 [群][Group].
* 联系对象, 即可以与 [Bot] 互动的对象. 包含 [用户][User], 和 [群][Group].
*/
abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot {
/**
* 这个联系所属 [Bot].
* 这个联系对象所属 [Bot].
*/
@WeakRefProperty
abstract val bot: Bot
......@@ -44,10 +44,7 @@ abstract class Contact : CoroutineScope, ContactJavaFriendlyAPI(), ContactOrBot
/**
* 可以是 QQ 号码或者群号码.
*
* 对于 [QQ], `uin` 与 `id` 是相同的意思.
* 对于 [Group], `groupCode` 与 `id` 是相同的意思.
*
* @see QQ.id
* @see User.id
* @see Group.id
*/
abstract override val id: Long
......
......@@ -11,8 +11,9 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.utils.*
import kotlin.jvm.JvmName
import net.mamoe.mirai.utils.LockFreeLinkedList
import net.mamoe.mirai.utils.asSequence
import kotlin.jvm.JvmField
/**
......@@ -20,9 +21,8 @@ import kotlin.jvm.JvmName
*
* @see ContactList.asSequence
*/
@OptIn(MiraiInternalAPI::class)
@Suppress("unused")
class ContactList<C : Contact>(@MiraiInternalAPI("Implementation may change in future release") val delegate: LockFreeLinkedList<C>) :
class ContactList<C : Contact> internal constructor(@JvmField internal val delegate: LockFreeLinkedList<C>) :
Iterable<C> {
operator fun get(id: Long): C =
delegate.asSequence().firstOrNull { it.id == id } ?: throw NoSuchElementException("Contact id $id")
......@@ -41,31 +41,6 @@ class ContactList<C : Contact>(@MiraiInternalAPI("Implementation may change in f
override fun iterator(): Iterator<C> {
return this.delegate.asSequence().iterator()
}
@PlannedRemoval("1.0.0")
@Suppress("PropertyName")
@get:JvmName("getIdContentString")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
val _idContentString: String
get() = this.idContentString
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
inline fun forEach(block: (C) -> Unit) = delegate.forEach(block)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun first(): C {
forEach { return it }
throw NoSuchElementException()
}
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun firstOrNull(): C? {
forEach { return it }
return null
}
}
/**
......@@ -76,64 +51,17 @@ class ContactList<C : Contact>(@MiraiInternalAPI("Implementation may change in f
* ```
*/
val ContactList<*>.idContentString: String
get() = "[" + @OptIn(MiraiInternalAPI::class) buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(
get() = "[" + buildString { delegate.forEach { append(it.id).append(", ") } }.dropLast(
2
) + "]"
@MiraiInternalAPI
operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
internal operator fun <C : Contact> LockFreeLinkedList<C>.get(id: Long): C {
forEach { if (it.id == id) return it }
throw NoSuchElementException("No such contact: $id")
}
@MiraiInternalAPI
fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
internal fun <C : Contact> LockFreeLinkedList<C>.getOrNull(id: Long): C? {
forEach { if (it.id == id) return it }
return null
}
@OptIn(MiraiInternalAPI::class)
@PlannedRemoval("1.0.0")
@Deprecated(
"use firstOrNull from stdlib",
replaceWith = ReplaceWith("this.asSequence().firstOrNull(filter)"),
level = DeprecationLevel.ERROR
)
inline fun <C : Contact> LockFreeLinkedList<C>.firstOrNull(filter: (C) -> Boolean): C? {
forEach { if (filter(it)) return it }
return null
}
@OptIn(MiraiInternalAPI::class)
@PlannedRemoval("1.0.0")
@Deprecated(
"use firstOrNull from stdlib",
replaceWith = ReplaceWith("firstOrNull(filter)"),
level = DeprecationLevel.ERROR
)
inline fun <C : Contact> LockFreeLinkedList<C>.filteringGetOrNull(filter: (C) -> Boolean): C? =
this.asSequence().firstOrNull(filter)
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toList from stdlib", level = DeprecationLevel.HIDDEN)
fun <E : Contact> ContactList<E>.toList(): List<E> = toMutableList()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toMutableList from stdlib", level = DeprecationLevel.HIDDEN)
@OptIn(MiraiInternalAPI::class)
fun <E : Contact> ContactList<E>.toMutableList(): MutableList<E> = this.delegate.toMutableList()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toSet from stdlib", level = DeprecationLevel.HIDDEN)
fun <E : Contact> ContactList<E>.toSet(): Set<E> = toMutableSet()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.toMutableSet from stdlib", level = DeprecationLevel.HIDDEN)
@OptIn(MiraiInternalAPI::class)
fun <E : Contact> ContactList<E>.toMutableSet(): MutableSet<E> = this.delegate.toMutableSet()
@PlannedRemoval("1.0.0")
@Deprecated("use Iterator.asSequence from stdlib", level = DeprecationLevel.HIDDEN)
@OptIn(MiraiInternalAPI::class)
fun <E : Contact> ContactList<E>.asSequence(): Sequence<E> = this.delegate.asSequence()
\ No newline at end of file
}
\ No newline at end of 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 号或群号.
......
......@@ -12,14 +12,12 @@
package net.mamoe.mirai.contact
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.utils.SinceMirai
/**
* 发送消息时消息过长抛出的异常.
*
* @see Contact.sendMessage
*/
@SinceMirai("0.32.0")
class MessageTooLargeException(
val target: Contact,
/**
......@@ -38,7 +36,6 @@ class MessageTooLargeException(
*
* @see Group.sendMessage
*/
@SinceMirai("0.33.0")
class BotIsBeingMutedException(
val target: Group
) : RuntimeException("bot is being muted, remaining ${target.botMuteRemaining} seconds")
......
......@@ -16,21 +16,23 @@ import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.toMessage
import kotlin.jvm.JvmSynthetic
/**
* 好友 对象.
* 注意: 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot].
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
* 代表一位好友.
*
* 对于同一个 [Bot] 任何一个人的 [Friend] 实例都是单一的.
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
* 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot].
* 对于同一个 [Bot], 任何一个人的 [Friend] 实例都是单一的.
* [Friend] 无法通过任何方式直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
*
* @see FriendMessageEvent
*/
@Suppress("DEPRECATION_ERROR")
abstract class Friend : QQ(), CoroutineScope {
abstract class Friend : User(), CoroutineScope {
/**
* 请求头像下载链接
*/
......
......@@ -47,7 +47,6 @@ abstract class Group : Contact(), CoroutineScope {
/**
* 群设置
*/
@SinceMirai("0.30.0")
abstract val settings: GroupSettings
/**
......@@ -80,7 +79,6 @@ abstract class Group : Contact(), CoroutineScope {
* 机器人在这个群里的权限
*
* @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限
* @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator]
*
* @see BotGroupPermissionChangeEvent 机器人群员修改
*/
......@@ -123,7 +121,6 @@ abstract class Group : Contact(), CoroutineScope {
* @return 退出成功时 true; 已经退出时 false
*/
@JvmSynthetic
@SinceMirai("0.37.0")
abstract suspend fun quit(): Boolean
/**
......@@ -197,7 +194,6 @@ abstract class Group : Contact(), CoroutineScope {
@Suppress("FunctionName")
@JvmName("quit")
@JavaFriendlyAPI
@SinceMirai("0.39.4")
fun __quitBlockingForJava__(): Boolean = runBlocking { quit() }
}
......@@ -206,7 +202,6 @@ abstract class Group : Contact(), CoroutineScope {
*
* @see Group.settings 获取群设置
*/
@SinceMirai("0.30.0")
interface GroupSettings {
/**
* 入群公告, 没有时为空字符串.
......
......@@ -27,4 +27,4 @@ expect abstract class ContactJavaFriendlyAPI internal constructor()
@Suppress("DEPRECATION_ERROR")
@MiraiInternalAPI
@JavaFriendlyAPI
expect abstract class MemberJavaFriendlyAPI internal constructor() : QQ // 将来会改为 User
\ No newline at end of file
expect abstract class MemberJavaFriendlyAPI internal constructor() : User
\ No newline at end of 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()
......
......@@ -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())
}
}
\ No newline at end of file
) = checkBotPermission(MemberPermission.ADMINISTRATOR, lazyMessage)
\ No newline at end of file
/*
* 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>
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:JvmMultifileClass
@file:JvmName("BotHelperKt")
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.data
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName
@MiraiExperimentalAPI
@Suppress("ClassName")
sealed class AddFriendResult {
abstract class DONE internal constructor() : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Done)"
}
/**
* 对方拒绝添加好友
*/
object REJECTED : AddFriendResult() {
override fun toString(): String = "AddFriendResult(Rejected)"
}
/**
* 这个人已经是好友
*/
object ALREADY_ADDED : DONE() {
override fun toString(): String = "AddFriendResult(AlreadyAdded)"
}
/**
* 等待对方同意
*/
object WAITING_FOR_APPROVAL : DONE() {
override fun toString(): String = "AddFriendResult(WaitingForApproval)"
}
/**
* 成功添加 (只在对方设置为允许任何人直接添加为好友时才会获得这个结果)
*/
object ADDED : DONE() {
override fun toString(): String = "AddFriendResult(Added)"
}
}
\ No newline at end of file
......@@ -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(
......
/*
* 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 = ", ")
}
\ No newline at end of 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)
}
\ No newline at end of file
}*/
\ No newline at end of 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")
......
......@@ -17,13 +17,11 @@ package net.mamoe.mirai.event
import net.mamoe.mirai.Bot
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.event.internal.*
import net.mamoe.mirai.message.ContactMessage
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.TempMessage
import net.mamoe.mirai.message.FriendMessageEvent
import net.mamoe.mirai.message.GroupMessageEvent
import net.mamoe.mirai.message.MessageEvent
import net.mamoe.mirai.message.TempMessageEvent
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.PlannedRemoval
import kotlin.js.JsName
import kotlin.jvm.JvmName
import kotlin.jvm.JvmOverloads
import kotlin.jvm.JvmSynthetic
......@@ -33,7 +31,7 @@ import kotlin.jvm.JvmSynthetic
* 消息事件的处理器.
*
* 注:
* 接受者 T 为 [ContactMessage]
* 接受者 T 为 [MessageEvent]
* 参数 String 为 转为字符串了的消息 ([Message.toString])
*/
typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
......@@ -49,7 +47,7 @@ typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R
* @see subscribeFriendMessages
*/
@MessageDsl
open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, RR>(
/**
* 用于 [MessageListener] 无返回值的替代.
*/
......@@ -233,7 +231,7 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
/** 如果是这个人发的消息. 消息目前只会是群消息 */
@MessageDsl
fun sentBy(name: String): ListeningFilter = content { this is GroupMessage && this.senderName == name }
fun sentBy(name: String): ListeningFilter = content { this is GroupMessageEvent && this.senderName == name }
/** 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */
@MessageDsl
......@@ -249,36 +247,37 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
/** 如果是好友发来的消息 */
@MessageDsl
fun sentByFriend(onEvent: MessageListener<FriendMessage, R>): Ret =
content({ this is FriendMessage }) { onEvent(this as FriendMessage, it) }
fun sentByFriend(onEvent: MessageListener<FriendMessageEvent, R>): Ret =
content({ this is FriendMessageEvent }) { onEvent(this as FriendMessageEvent, it) }
/** 如果是好友发来的消息 */
@MessageDsl
fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessage }
fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessageEvent }
/** 如果是好友发来的消息 */
/** 如果是群临时会话消息 */
@MessageDsl
fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessage }
fun sentByTemp(): ListeningFilter = newListeningFilter { this is TempMessageEvent }
/** 如果是管理员或群主发的消息 */
@MessageDsl
fun sentByOperator(): ListeningFilter = content { this is GroupMessage && sender.permission.isOperator() }
fun sentByOperator(): ListeningFilter = content { this is GroupMessageEvent && sender.permission.isOperator() }
/** 如果是管理员发的消息 */
@MessageDsl
fun sentByAdministrator(): ListeningFilter = content { this is GroupMessage && sender.permission.isAdministrator() }
fun sentByAdministrator(): ListeningFilter =
content { this is GroupMessageEvent && sender.permission.isAdministrator() }
/** 如果是群主发的消息 */
@MessageDsl
fun sentByOwner(): ListeningFilter = content { this is GroupMessage && sender.isOwner() }
fun sentByOwner(): ListeningFilter = content { this is GroupMessageEvent && sender.isOwner() }
/** 如果是来自这个群的消息 */
@MessageDsl
fun sentFrom(groupId: Long): ListeningFilter = content { this is GroupMessage && group.id == groupId }
fun sentFrom(groupId: Long): ListeningFilter = content { this is GroupMessageEvent && group.id == groupId }
/** 如果是来自这个群的消息 */
@MessageDsl
fun sentFrom(group: Group): ListeningFilter = content { this is GroupMessage && group.id == group.id }
fun sentFrom(group: Group): ListeningFilter = content { this is GroupMessageEvent && group.id == group.id }
/** [消息内容][Message.contentToString]包含目标为 [Bot] 的 [At] */
@MessageDsl
......@@ -440,79 +439,6 @@ open class MessageSubscribersBuilder<M : ContactMessage, out Ret, R : RR, RR>(
}
return stub
}
/**
* 不考虑空格, [消息内容][Message.contentToString]以 [this] 开始则执行 [replier] 并将其返回值回复给发信对象.
* @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他类型则 [Any.toString] 后回复
*/
@PlannedRemoval("1.0.0")
@Deprecated("use startsWith on your own", replaceWith = ReplaceWith("startsWith(this, true, true, replier)"))
open infix fun String.startsWithReply(replier: @MessageDsl suspend M.(String) -> Any?): Ret {
val toCheck = this.trimStart()
return content({ it.trim().startsWith(toCheck) }, {
executeAndReply(this) { replier(this, it.trim().removePrefix(toCheck)) }
})
}
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun contains(message: Message, onEvent: MessageListener<M, R>): Ret {
return content({ this.message.any { it == message } }, onEvent)
}
@JvmName("case1")
@JsName("case1")
@PlannedRemoval("1.0.0")
@Deprecated("use String.invoke", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this(block)"))
infix fun String.`->`(block: MessageListener<M, R>): Ret {
return this(block)
}
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun containsAll(
vararg sub: String,
ignoreCase: Boolean = false,
trim: Boolean = true,
onEvent: MessageListener<M, R>
): Ret = containsAllImpl(sub, ignoreCase = ignoreCase, trim = trim).invoke(onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun containsAny(
vararg sub: String,
ignoreCase: Boolean = false,
trim: Boolean = true,
onEvent: MessageListener<M, R>
): Ret = containsAnyImpl(*sub, ignoreCase = ignoreCase, trim = trim).invoke(onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentBy(name: String, onEvent: MessageListener<M, R>): Ret =
content({ (this as? GroupMessage)?.senderName == name }, onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentByOperator(onEvent: MessageListener<M, R>): Ret =
content({ this is GroupMessage && this.sender.isOperator() }, onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentByAdministrator(onEvent: MessageListener<M, R>): Ret =
content({ this is GroupMessage && this.sender.isAdministrator() }, onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentByOwner(onEvent: MessageListener<M, R>): Ret =
content({ this is GroupMessage && this.sender.isOwner() }, onEvent)
@PlannedRemoval("1.0.0")
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
fun sentFrom(groupId: Long, onEvent: MessageListener<GroupMessage, R>): Ret =
content({ this is GroupMessage && this.group.id == groupId }) { onEvent(this as GroupMessage, it) }
}
/**
......
......@@ -25,7 +25,9 @@ import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.qqandroid.network.Packet
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.ExternalImage
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.internal.runBlocking
import kotlin.jvm.JvmField
import kotlin.jvm.JvmName
......@@ -75,7 +77,6 @@ sealed class BotOfflineEvent : BotEvent, AbstractEvent() {
/**
* 服务器主动要求更换另一个服务器
*/
@SinceMirai("0.37.1")
data class RequireReconnect(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent
}
......@@ -131,7 +132,6 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
* 消息内部 id.
* @see MessageSource.id
*/
@SinceMirai("0.39.0")
abstract val messageInternalId: Int
/**
......@@ -148,7 +148,7 @@ sealed class MessageRecallEvent : BotEvent, AbstractEvent() {
override val messageInternalId: Int,
override val messageTime: Int,
/**
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id]
* 撤回操作人, 可能为 [Bot.uin] 或好友的 [User.id]
*/
val operator: Long
) : MessageRecallEvent(), Packet {
......@@ -235,20 +235,17 @@ sealed class ImageUploadEvent : BotEvent, BotActiveEvent, AbstractEvent() {
/**
* 机器人被踢出群或在其他客户端主动退出一个群. 在事件广播前 [Bot.groups] 就已删除这个群.
*/
@SinceMirai("0.36.0")
sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent() {
abstract val group: Group
/**
* 机器人主动退出一个群.
*/
@SinceMirai("0.37.0")
data class Active(override val group: Group) : BotLeaveEvent()
/**
* 机器人被管理员或群主踢出群. 暂不支持获取操作人
*/
@SinceMirai("0.37.0")
data class Kick(override val group: Group) : BotLeaveEvent()
override val bot: Bot get() = group.bot
......@@ -321,7 +318,6 @@ data class GroupNameChangeEvent(
/**
* 操作人. 为 null 时则是机器人操作
*/
@SinceMirai("0.37.3")
override val operator: Member?
) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent() {
@Deprecated("for binary compatibility", level = DeprecationLevel.HIDDEN)
......@@ -409,13 +405,11 @@ sealed class MemberJoinEvent(override val member: Member) : GroupMemberEvent, Bo
/**
* 被邀请加入群
*/
@SinceMirai("0.36.0")
data class Invite(override val member: Member) : MemberJoinEvent(member)
/**
* 成员主动加入群
*/
@SinceMirai("0.36.0")
data class Active(override val member: Member) : MemberJoinEvent(member)
}
......@@ -451,7 +445,6 @@ sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() {
/**
* [Bot] 被邀请加入一个群.
*/
@SinceMirai("0.39.4")
data class BotInvitedJoinGroupRequestEvent(
override val bot: Bot,
/**
......@@ -494,7 +487,6 @@ data class BotInvitedJoinGroupRequestEvent(
/**
* 一个账号请求加入群事件, [Bot] 在此群中是管理员或群主.
*/
@SinceMirai("0.35.0")
data class MemberJoinRequestEvent(
override val bot: Bot,
/**
......@@ -566,17 +558,8 @@ data class MemberCardChangeEvent(
*/
val new: String,
override val member: Member,
/**
* 此事件无法确定操作人, 将在未来版本删除
*/
@PlannedRemoval("1.0.0")
@Deprecated("operator is always unknown", level = DeprecationLevel.ERROR)
override val operator: Member?
) : GroupMemberEvent, Packet, AbstractEvent(),
@Deprecated("operator is always unknown", level = DeprecationLevel.ERROR)
GroupOperableEvent
override val member: Member
) : GroupMemberEvent, Packet, AbstractEvent()
/**
* 成员群头衔改动. 一定为群主操作
......@@ -623,6 +606,8 @@ data class MemberPermissionChangeEvent(
/**
* 群成员被禁言事件. 被禁言的成员都不可能是机器人本人
*
* @see BotMuteEvent 机器人被禁言的事件
*/
data class MemberMuteEvent(
override val member: Member,
......@@ -635,6 +620,8 @@ data class MemberMuteEvent(
/**
* 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人
*
* @see BotUnmuteEvent 机器人被取消禁言的事件
*/
data class MemberUnmuteEvent(
override val member: Member,
......@@ -655,65 +642,36 @@ data class MemberUnmuteEvent(
/**
* 好友昵称改变事件. 目前仅支持解析 (来自 PC 端的修改).
*/
@SinceMirai("0.36.0")
data class FriendRemarkChangeEvent(
override val bot: Bot,
val friend: Friend,
override val friend: Friend,
val newName: String
) : BotEvent, Packet, AbstractEvent() {
@PlannedRemoval("1.0.0")
@Deprecated("", level = DeprecationLevel.HIDDEN)
@get:JvmSynthetic
@get:JvmName("getFriend")
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
val friendDeprecated: QQ
get() = friend
}
) : FriendEvent, Packet, AbstractEvent()
/**
* 成功添加了一个新好友的事件
*/
@SinceMirai("0.36.0")
data class FriendAddEvent(
/**
* 新好友. 已经添加到 [Bot.friends]
*/
val friend: Friend
) : BotEvent, Packet, AbstractEvent() {
override val friend: Friend
) : FriendEvent, Packet, AbstractEvent() {
override val bot: Bot get() = friend.bot
@PlannedRemoval("1.0.0")
@Deprecated("", level = DeprecationLevel.HIDDEN)
@get:JvmSynthetic
@get:JvmName("getFriend")
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
val friendDeprecated: QQ
get() = friend
}
/**
* 好友已被删除的事件.
*/
@SinceMirai("0.36.0")
data class FriendDeleteEvent(
val friend: Friend
) : BotEvent, Packet, AbstractEvent() {
override val friend: Friend
) : FriendEvent, Packet, AbstractEvent() {
override val bot: Bot get() = friend.bot
@PlannedRemoval("1.0.0")
@Deprecated("", level = DeprecationLevel.HIDDEN)
@get:JvmSynthetic
@get:JvmName("getFriend")
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
val friendDeprecated: QQ
get() = friend
}
/**
* 一个账号请求添加机器人为好友的事件
*/
@SinceMirai("0.35.0")
data class NewFriendRequestEvent(
override val bot: Bot,
/**
......@@ -725,7 +683,7 @@ data class NewFriendRequestEvent(
*/
val message: String,
/**
* 请求人 [QQ.id]
* 请求人 [User.id]
*/
val fromId: Long,
/**
......
......@@ -15,8 +15,6 @@ import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/**
* 有关一个 [Bot] 的事件
......@@ -90,11 +88,4 @@ interface FriendEvent : BotEvent {
val friend: Friend
override val bot: Bot
get() = friend.bot
@Deprecated("", level = DeprecationLevel.HIDDEN)
@get:JvmSynthetic
@get:JvmName("getFriend")
@Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR")
val friendDeprecated: net.mamoe.mirai.contact.QQ
get() = friend
}
\ No newline at end of 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,
......
......@@ -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) {
cont.resumeWith(kotlin.runCatching {
mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING
})
try {
cont.resumeWith(kotlin.runCatching {
mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING
})
} catch (e: Exception) {
}
return@subscribe ListeningStatus.STOPPED
}
}
\ No newline at end of file
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment