Commit 71225648 authored by ryoii's avatar ryoii

Merge remote-tracking branch 'origin/master'

parents 02f6d3d0 0126e4ce
---
name: 特性申请
about: 申请 mirai 添加新的特性
title: ''
labels: feature
assignees: ''
---
以下相关功能将会被直接拒绝:
- 资金相关: 红包, 转账
- 主动加好友, 主动加群, 主动邀请加入群
可以提交的内容:
- 有较高使用频率的协议 (低频功能不接受提议)
- 架构 / 功能上的建议 (欢迎)
请删除以上内容后再描述问题
---
name: Bug 报告
about: 提交一个 bug
title: ''
labels: " bug "
assignees: ''
---
提交前请先确认:
- 已经在现有 issue 列表中检查并未发现重复问题
- 确认问题属于 `mirai-core`, 而不是 `mirai-console` 或其他
若有报错, 请尽可能附加完整控制台日志或截图
若无报错, 请尽可能描述如何复现这个问题
请删除以上内容后再描述问题
# This is a basic workflow to help you get started with Actions
name: Shadow
# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
release:
types:
- created
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
# This workflow contains a single job called "build"
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle and shadowJar
run: ./gradlew :mirai-core:shadowJar :mirai-core-qqandroid:shadowJar
- name: Upload artifact
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: mirai-core-all
# Directory containing files to upload
path: "mirai-core/build/libs/mirai-core-*-all.jar"
- name: Upload artifact
uses: actions/upload-artifact@v1.0.0
with:
# Artifact name
name: mirai-core-qqandroid-all
# Directory containing files to upload
path: "mirai-core-qqandroid/build/libs/mirai-core-qqandroid-*-all.jar"
...@@ -2,12 +2,29 @@ ...@@ -2,12 +2,29 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `0.25.0` 还未发布 ## `0.27.0` 2020/3/8
- 支持 `XML`, `Json`, `LightApp``RichMessage`
## `0.26.2` 2020/3/8
- 新增 `MessageChain.repeat``MessageChain.times`
- JVM 平台下 `PlatformLogger` 可重定向输出
- 修复 `NullMessageChain.equals` 判断不正确的问题
- 新增 `PlainText.of` 以应对一些特殊情况
## `0.26.1` 2020/3/8
- 重写 Jce 序列化, 提升反序列性能
- 更新 `Kotlin` 版本到 1.3.70
- 更新 `kotlinx.coroutines`, `atomicfu`, `kotlinx.coroutines` 依赖版本
## `0.26.0` 2020/3/7
- 使用 `kotlinx.io` 而不是 `ktor.io`
- 修复 #111, #108, #116, #112
## `0.25.0` 2020/3/6
- 适配 8.2.7 版本(2020 年 3 月)协议 - 适配 8.2.7 版本(2020 年 3 月)协议
- 全面的 `Image` 类型: Online/Offline Image, Friend/Group Image - 全面的 `Image` 类型: Online/Offline Image, Friend/Group Image
- 修复查询图片链接时好友图片链接错误的问题 - 修复查询图片链接时好友图片链接错误的问题
- Kotlin 1.3.70 - 修复 bugs: #105, #106, #107
- 修复事件处理错误 (#107)
## `0.24.1` 2020/3/3 ## `0.24.1` 2020/3/3
- 修复 `Member` 的委托 `QQ` 弱引用被释放的问题 - 修复 `Member` 的委托 `QQ` 弱引用被释放的问题
......
...@@ -61,7 +61,7 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos) ...@@ -61,7 +61,7 @@ Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
### 使用者 ### 使用者
- [mirai-console](https://github.com/mamoe/mirai/tree/master/mirai-console) 支持插件 **本模块正在完善** - [mirai-console](https://github.com/mamoe/mirai-console) 支持插件 **本模块正在完善**
### 我是其他平台的使用者 ### 我是其他平台的使用者
......
...@@ -4,12 +4,11 @@ import java.util.* ...@@ -4,12 +4,11 @@ import java.util.*
buildscript { buildscript {
repositories { repositories {
mavenLocal() mavenLocal()
maven { setUrl("https://mirrors.huaweicloud.com/repository/maven") } maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter() jcenter()
mavenCentral() // mavenCentral()
google() google()
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } // maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
} }
dependencies { dependencies {
...@@ -41,11 +40,10 @@ allprojects { ...@@ -41,11 +40,10 @@ allprojects {
repositories { repositories {
mavenLocal() mavenLocal()
maven { setUrl("https://mirrors.huaweicloud.com/repository/maven") } maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter() jcenter()
mavenCentral() // mavenCentral()
google() google()
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-eap") } // maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
maven { setUrl("https://dl.bintray.com/kotlin/kotlin-dev") }
} }
} }
\ No newline at end of file
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
miraiVersion=0.24.1 miraiVersion=0.27.0
kotlin.incremental.multiplatform=true kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true kotlin.parallel.tasks.in.project=true
# kotlin # kotlin
kotlinVersion=1.3.61 kotlinVersion=1.3.70
# kotlin libraries # kotlin libraries
serializationVersion=0.14.0 serializationVersion=0.20.0
coroutinesVersion=1.3.3 coroutinesVersion=1.3.4
atomicFuVersion=0.14.1 atomicFuVersion=0.14.1
kotlinXIoVersion=0.1.16
coroutinesIoVersion=0.1.16 coroutinesIoVersion=0.1.16
# utility # utility
ktorVersion=1.3.1 ktorVersion=1.3.1
\ No newline at end of file
...@@ -8,9 +8,12 @@ plugins { ...@@ -8,9 +8,12 @@ plugins {
id("com.jfrog.bintray") version "1.8.4-jetbrains-3" id("com.jfrog.bintray") version "1.8.4-jetbrains-3"
} }
apply(plugin = "com.github.johnrengelman.shadow")
val kotlinVersion: String by rootProject.ext val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext val coroutinesIoVersion: String by rootProject.ext
...@@ -66,6 +69,7 @@ kotlin { ...@@ -66,6 +69,7 @@ kotlin {
api(kotlin("stdlib", kotlinVersion)) api(kotlin("stdlib", kotlinVersion))
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
api(kotlinx("io", kotlinXIoVersion))
api(kotlinx("coroutines-io", coroutinesIoVersion)) api(kotlinx("coroutines-io", coroutinesIoVersion))
api(kotlinx("coroutines-core", coroutinesVersion)) api(kotlinx("coroutines-core", coroutinesVersion))
} }
...@@ -73,6 +77,7 @@ kotlin { ...@@ -73,6 +77,7 @@ kotlin {
commonMain { commonMain {
dependencies { dependencies {
api(kotlinx("serialization-runtime-common", serializationVersion)) api(kotlinx("serialization-runtime-common", serializationVersion))
api(kotlinx("serialization-protobuf-common", serializationVersion))
} }
} }
commonTest { commonTest {
...@@ -86,6 +91,7 @@ kotlin { ...@@ -86,6 +91,7 @@ kotlin {
if (isAndroidSDKAvailable) { if (isAndroidSDKAvailable) {
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
api(kotlinx("serialization-protobuf", serializationVersion))
} }
} }
......
...@@ -21,7 +21,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI ...@@ -21,7 +21,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
*/ */
@Suppress("INAPPLICABLE_JVM_NAME") @Suppress("INAPPLICABLE_JVM_NAME")
actual object QQAndroid : BotFactory { actual object QQAndroid : BotFactory {
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@JvmName("newBot") @JvmName("newBot")
actual override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot { actual override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot {
return QQAndroidBot(context, BotAccount(qq, password), configuration) return QQAndroidBot(context, BotAccount(qq, password), configuration)
...@@ -30,7 +30,7 @@ actual object QQAndroid : BotFactory { ...@@ -30,7 +30,7 @@ actual object QQAndroid : BotFactory {
/** /**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例 * 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/ */
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@JvmName("newBot") @JvmName("newBot")
actual override fun Bot( actual override fun Bot(
context: Context, context: Context,
......
...@@ -9,15 +9,188 @@ ...@@ -9,15 +9,188 @@
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.consumeEachBufferRange
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readBytes
import kotlinx.coroutines.io.*
import kotlinx.io.core.*
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context import net.mamoe.mirai.utils.Context
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.toReadPacket
import java.nio.ByteBuffer
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal actual class QQAndroidBot internal actual class QQAndroidBot
actual constructor( actual constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
configuration: BotConfiguration configuration: BotConfiguration
) : QQAndroidBotBase(context, account, configuration) ) : QQAndroidBotBase(context, account, configuration)
\ No newline at end of file
@OptIn(MiraiInternalAPI::class)
@Suppress("DEPRECATION")
internal actual fun ByteReadChannel.toKotlinByteReadChannel(): kotlinx.coroutines.io.ByteReadChannel {
return object : kotlinx.coroutines.io.ByteReadChannel {
override val availableForRead: Int
get() = this@toKotlinByteReadChannel.availableForRead
override val isClosedForRead: Boolean
get() = this@toKotlinByteReadChannel.isClosedForRead
override val isClosedForWrite: Boolean
get() = this@toKotlinByteReadChannel.isClosedForWrite
@Suppress("DEPRECATION_ERROR", "OverridingDeprecatedMember")
override var readByteOrder: ByteOrder
get() = when (this@toKotlinByteReadChannel.readByteOrder) {
io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN -> ByteOrder.BIG_ENDIAN
io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN -> ByteOrder.LITTLE_ENDIAN
}
set(value) {
this@toKotlinByteReadChannel.readByteOrder = when (value) {
ByteOrder.BIG_ENDIAN -> io.ktor.utils.io.core.ByteOrder.BIG_ENDIAN
ByteOrder.LITTLE_ENDIAN -> io.ktor.utils.io.core.ByteOrder.LITTLE_ENDIAN
}
}
@Suppress("DEPRECATION_ERROR", "DEPRECATION", "OverridingDeprecatedMember")
override val totalBytesRead: Long
get() = this@toKotlinByteReadChannel.totalBytesRead
override fun cancel(cause: Throwable?): Boolean = this@toKotlinByteReadChannel.cancel(cause)
override suspend fun consumeEachBufferRange(visitor: ConsumeEachBufferVisitor) =
this@toKotlinByteReadChannel.consumeEachBufferRange(visitor)
override suspend fun discard(max: Long): Long = this@toKotlinByteReadChannel.discard(max)
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override fun <R> lookAhead(visitor: LookAheadSession.() -> R): R {
return this@toKotlinByteReadChannel.lookAhead l@{
visitor(object : LookAheadSession {
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
}
@Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE")
@ExperimentalIoApi
override suspend fun <R> lookAheadSuspend(visitor: suspend LookAheadSuspendSession.() -> R): R =
this@toKotlinByteReadChannel.lookAheadSuspend l@{
visitor(object : LookAheadSuspendSession {
override suspend fun awaitAtLeast(n: Int): Boolean {
return this@l.awaitAtLeast(n)
}
override fun consumed(n: Int) {
return this@l.consumed(n)
}
override fun request(skip: Int, atLeast: Int): ByteBuffer? {
return this@l.request(skip, atLeast)
}
})
}
override suspend fun read(min: Int, consumer: (ByteBuffer) -> Unit) =
this@toKotlinByteReadChannel.read(min, consumer)
override suspend fun readAvailable(dst: ByteBuffer): Int = this@toKotlinByteReadChannel.readAvailable(dst)
override suspend fun readAvailable(dst: ByteArray, offset: Int, length: Int): Int =
this@toKotlinByteReadChannel.readAvailable(dst, offset, length)
override suspend fun readAvailable(dst: IoBuffer): Int {
ByteArrayPool.useInstance {
val read = this@toKotlinByteReadChannel.readAvailable(it, 0, it.size)
dst.writeFully(it, 0, read)
return read
}
}
override suspend fun readBoolean(): Boolean = this@toKotlinByteReadChannel.readBoolean()
override suspend fun readByte(): Byte = this@toKotlinByteReadChannel.readByte()
override suspend fun readDouble(): Double = this@toKotlinByteReadChannel.readDouble()
override suspend fun readFloat(): Float = this@toKotlinByteReadChannel.readFloat()
override suspend fun readFully(dst: ByteBuffer): Int {
TODO("not implemented")
}
override suspend fun readFully(dst: ByteArray, offset: Int, length: Int) =
this@toKotlinByteReadChannel.readFully(dst, offset, length)
override suspend fun readFully(dst: IoBuffer, n: Int) {
ByteArrayPool.useInstance {
dst.writeFully(it, 0, this.readAvailable(it, 0, it.size))
}
}
override suspend fun readInt(): Int = this@toKotlinByteReadChannel.readInt()
override suspend fun readLong(): Long = this@toKotlinByteReadChannel.readLong()
override suspend fun readPacket(size: Int, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readPacket(size, headerSizeHint).readBytes().toReadPacket()
}
override suspend fun readRemaining(limit: Long, headerSizeHint: Int): ByteReadPacket {
return this@toKotlinByteReadChannel.readRemaining(limit, headerSizeHint).readBytes().toReadPacket()
}
@OptIn(ExperimentalIoApi::class)
@ExperimentalIoApi
override fun readSession(consumer: ReadSession.() -> Unit) {
@Suppress("DEPRECATION")
this@toKotlinByteReadChannel.readSession lambda@{
consumer(object : ReadSession {
override val availableForRead: Int
get() = this@lambda.availableForRead
override fun discard(n: Int): Int = this@lambda.discard(n)
override fun request(atLeast: Int): IoBuffer? {
val ioBuffer: io.ktor.utils.io.core.IoBuffer = this@lambda.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
}
override suspend fun readShort(): Short = this@toKotlinByteReadChannel.readShort()
@Suppress("EXPERIMENTAL_OVERRIDE", "EXPERIMENTAL_API_USAGE")
@ExperimentalIoApi
override suspend fun readSuspendableSession(consumer: suspend SuspendableReadSession.() -> Unit) =
this@toKotlinByteReadChannel.readSuspendableSession l@{
consumer(object : SuspendableReadSession {
override val availableForRead: Int
get() = this@l.availableForRead
override suspend fun await(atLeast: Int): Boolean = this@l.await(atLeast)
override fun discard(n: Int): Int = this@l.discard(n)
override fun request(atLeast: Int): IoBuffer? {
@Suppress("DuplicatedCode") val ioBuffer: io.ktor.utils.io.core.IoBuffer =
this@l.request(atLeast) ?: return null
val buffer = IoBuffer.Pool.borrow()
val bytes = (ioBuffer as Input).readBytes()
buffer.writeFully(bytes)
return buffer
}
})
}
override suspend fun readUTF8Line(limit: Int): String? = this@toKotlinByteReadChannel.readUTF8Line(limit)
override suspend fun <A : Appendable> readUTF8LineTo(out: A, limit: Int): Boolean =
this@toKotlinByteReadChannel.readUTF8LineTo(out, limit)
}
}
\ No newline at end of file
...@@ -9,9 +9,9 @@ ...@@ -9,9 +9,9 @@
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import io.ktor.utils.io.core.Closeable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.io.core.Closeable
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.* import net.mamoe.mirai.data.*
...@@ -78,6 +78,7 @@ internal class QQImpl( ...@@ -78,6 +78,7 @@ internal class QQImpl(
return MessageReceipt(source, this, null) return MessageReceipt(source, this, null)
} }
@OptIn(MiraiInternalAPI::class)
override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = try { override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = try {
if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) { if (BeforeImageUploadEvent(this, image).broadcast().isCancelled) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
...@@ -111,7 +112,7 @@ internal class QQImpl( ...@@ -111,7 +112,7 @@ internal class QQImpl(
ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast() ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast()
} }
is LongConn.OffPicUp.Response.RequireUpload -> { is LongConn.OffPicUp.Response.RequireUpload -> {
Http.postImage( MiraiPlatformUtils.Http.postImage(
"0x6ff0070", "0x6ff0070",
bot.uin, bot.uin,
null, null,
...@@ -358,7 +359,7 @@ internal class MemberInfoImpl( ...@@ -358,7 +359,7 @@ internal class MemberInfoImpl(
override val muteTimestamp: Int = jceInfo.dwShutupTimestap?.toInt() ?: 0 override val muteTimestamp: Int = jceInfo.dwShutupTimestap?.toInt() ?: 0
} }
@UseExperimental(ExperimentalContracts::class) @OptIn(ExperimentalContracts::class)
internal fun GroupImpl.Companion.checkIsInstance(expression: Boolean) { internal fun GroupImpl.Companion.checkIsInstance(expression: Boolean) {
contract { contract {
returns() implies expression returns() implies expression
...@@ -367,13 +368,14 @@ internal fun GroupImpl.Companion.checkIsInstance(expression: Boolean) { ...@@ -367,13 +368,14 @@ internal fun GroupImpl.Companion.checkIsInstance(expression: Boolean) {
} }
@Suppress("PropertyName") @Suppress("PropertyName")
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal class GroupImpl( internal class GroupImpl(
bot: QQAndroidBot, override val coroutineContext: CoroutineContext, bot: QQAndroidBot, override val coroutineContext: CoroutineContext,
override val id: Long, override val id: Long,
groupInfo: GroupInfo, groupInfo: GroupInfo,
members: Sequence<MemberInfo> members: Sequence<MemberInfo>
) : Group() { ) : Group() {
@Suppress("\"RemoveEmptyClassBody\"") // things will go wrong after removal, don't try
companion object { companion object {
} }
...@@ -383,7 +385,7 @@ internal class GroupImpl( ...@@ -383,7 +385,7 @@ internal class GroupImpl(
override lateinit var owner: Member override lateinit var owner: Member
@UseExperimental(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override val botAsMember: Member by lazy { override val botAsMember: Member by lazy {
Member(object : MemberInfo { Member(object : MemberInfo {
override val nameCard: String override val nameCard: String
...@@ -401,7 +403,7 @@ internal class GroupImpl( ...@@ -401,7 +403,7 @@ internal class GroupImpl(
}) })
} }
@UseExperimental(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override lateinit var botPermission: MemberPermission override lateinit var botPermission: MemberPermission
var _botMuteTimestamp: Int = groupInfo.botMuteRemaining var _botMuteTimestamp: Int = groupInfo.botMuteRemaining
...@@ -557,10 +559,10 @@ internal class GroupImpl( ...@@ -557,10 +559,10 @@ internal class GroupImpl(
TODO("not implemented") TODO("not implemented")
} }
@UseExperimental(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override fun Member(memberInfo: MemberInfo): Member { override fun Member(memberInfo: MemberInfo): Member {
return MemberImpl( return MemberImpl(
@UseExperimental(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
bot._lowLevelNewQQ(memberInfo) as QQImpl, bot._lowLevelNewQQ(memberInfo) as QQImpl,
this, this,
this.coroutineContext, this.coroutineContext,
......
...@@ -11,8 +11,8 @@ package net.mamoe.mirai.qqandroid ...@@ -11,8 +11,8 @@ package net.mamoe.mirai.qqandroid
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse import io.ktor.client.statement.HttpResponse
import io.ktor.utils.io.ByteReadChannel
import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.io.ByteReadChannel
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.BotImpl import net.mamoe.mirai.BotImpl
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
...@@ -36,14 +36,14 @@ import net.mamoe.mirai.utils.* ...@@ -36,14 +36,14 @@ import net.mamoe.mirai.utils.*
import kotlin.collections.asSequence import kotlin.collections.asSequence
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal expect class QQAndroidBot constructor( internal expect class QQAndroidBot constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
configuration: BotConfiguration configuration: BotConfiguration
) : QQAndroidBotBase ) : QQAndroidBotBase
@UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal abstract class QQAndroidBotBase constructor( internal abstract class QQAndroidBotBase constructor(
context: Context, context: Context,
account: BotAccount, account: BotAccount,
...@@ -69,7 +69,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -69,7 +69,7 @@ internal abstract class QQAndroidBotBase constructor(
override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList()) override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList())
override val selfQQ: QQ by lazy { override val selfQQ: QQ by lazy {
@UseExperimental(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
_lowLevelNewQQ(object : FriendInfo { _lowLevelNewQQ(object : FriendInfo {
override val uin: Long get() = this@QQAndroidBotBase.uin override val uin: Long get() = this@QQAndroidBotBase.uin
override val nick: String get() = this@QQAndroidBotBase.nick override val nick: String get() = this@QQAndroidBotBase.nick
...@@ -101,7 +101,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -101,7 +101,7 @@ internal abstract class QQAndroidBotBase constructor(
return groups.delegate.getOrNull(uin) return groups.delegate.getOrNull(uin)
} }
@UseExperimental(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupList(): Sequence<Long> { override suspend fun _lowLevelQueryGroupList(): Sequence<Long> {
return network.run { return network.run {
FriendList.GetTroopListSimplify(bot.client) FriendList.GetTroopListSimplify(bot.client)
...@@ -109,7 +109,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -109,7 +109,7 @@ internal abstract class QQAndroidBotBase constructor(
}.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode } }.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
} }
@UseExperimental(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo = network.run { override suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo = network.run {
TroopManagement.GetGroupInfo( TroopManagement.GetGroupInfo(
client = bot.client, client = bot.client,
...@@ -117,7 +117,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -117,7 +117,7 @@ internal abstract class QQAndroidBotBase constructor(
).sendAndExpect<GroupInfoImpl>(retry = 2) ).sendAndExpect<GroupInfoImpl>(retry = 2)
} }
@UseExperimental(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupMemberList( override suspend fun _lowLevelQueryGroupMemberList(
groupUin: Long, groupUin: Long,
groupCode: Long, groupCode: Long,
...@@ -187,7 +187,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -187,7 +187,7 @@ internal abstract class QQAndroidBotBase constructor(
} }
} }
@UseExperimental(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long) { override suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long) {
network.run { network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response = val response: PbMessageSvc.PbMsgWithDraw.Response =
...@@ -198,7 +198,7 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -198,7 +198,7 @@ internal abstract class QQAndroidBotBase constructor(
} }
} }
@UseExperimental(LowLevelAPI::class) @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelRecallGroupMessage(groupId: Long, messageId: Long) { override suspend fun _lowLevelRecallGroupMessage(groupId: Long, messageId: Long) {
network.run { network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response = val response: PbMessageSvc.PbMsgWithDraw.Response =
...@@ -222,6 +222,10 @@ internal abstract class QQAndroidBotBase constructor( ...@@ -222,6 +222,10 @@ internal abstract class QQAndroidBotBase constructor(
} }
override suspend fun openChannel(image: Image): ByteReadChannel { override suspend fun openChannel(image: Image): ByteReadChannel {
return Http.get<HttpResponse>(queryImageUrl(image)).content return MiraiPlatformUtils.Http.get<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel()
} }
} }
\ No newline at end of file
@Suppress("DEPRECATION")
@OptIn(MiraiInternalAPI::class)
internal expect fun io.ktor.utils.io.ByteReadChannel.toKotlinByteReadChannel(): ByteReadChannel
\ No newline at end of file
package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.core.Input
import kotlinx.io.core.Output
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialFormat
import kotlinx.serialization.SerializationStrategy
interface IOFormat : SerialFormat {
fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output)
fun <T> load(deserializer: DeserializationStrategy<T>, input: Input): T
}
...@@ -5,11 +5,19 @@ ...@@ -5,11 +5,19 @@
* Some code changed by Mamoe is annotated around "MIRAI MODIFY START" and "MIRAI MODIFY END" * Some code changed by Mamoe is annotated around "MIRAI MODIFY START" and "MIRAI MODIFY END"
*/ */
@file:Suppress("DEPRECATION_ERROR")
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.* import kotlinx.io.ByteArrayOutputStream
import kotlinx.io.ByteBuffer
import kotlinx.io.ByteOrder
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.internal.* import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.MapEntrySerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.internal.MapLikeSerializer
import kotlinx.serialization.internal.TaggedEncoder
import kotlinx.serialization.modules.EmptyModule import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
...@@ -33,15 +41,19 @@ internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefa ...@@ -33,15 +41,19 @@ internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefa
* *
* 代码复制自 kotlinx.serialization. 修改部分已进行标注 (详见 "MIRAI MODIFY START") * 代码复制自 kotlinx.serialization. 修改部分已进行标注 (详见 "MIRAI MODIFY START")
*/ */
class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat { @OptIn(InternalSerializationApi::class)
class ProtoBufWithNullableSupport(override val context: SerialModule = EmptyModule) : SerialFormat, BinaryFormat {
internal open inner class ProtobufWriter(val encoder: ProtobufEncoder) : TaggedEncoder<ProtoDesc>() { internal open inner class ProtobufWriter(private val encoder: ProtobufEncoder) : TaggedEncoder<ProtoDesc>() {
public override val context override val context
get() = this@ProtoBufWithNullableSupport.context get() = this@ProtoBufWithNullableSupport.context
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder = when (desc.kind) { override fun beginStructure(
descriptor: SerialDescriptor,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder = when (descriptor.kind) {
StructureKind.LIST -> RepeatedWriter(encoder, currentTag) StructureKind.LIST -> RepeatedWriter(encoder, currentTag)
StructureKind.CLASS, UnionKind.OBJECT, is PolymorphicKind -> ObjectWriter(currentTagOrNull, encoder) StructureKind.CLASS, StructureKind.OBJECT, is PolymorphicKind -> ObjectWriter(currentTagOrNull, encoder)
StructureKind.MAP -> MapRepeatedWriter(currentTagOrNull, encoder) StructureKind.MAP -> MapRepeatedWriter(currentTagOrNull, encoder)
else -> throw SerializationException("Primitives are not supported at top-level") else -> throw SerializationException("Primitives are not supported at top-level")
} }
...@@ -82,12 +94,15 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac ...@@ -82,12 +94,15 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING") @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when { override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
// encode maps as collection of map entries, not merged collection of key-values // encode maps as collection of map entries, not merged collection of key-values
serializer.descriptor is MapLikeDescriptor -> { serializer.descriptor.kind == StructureKind.MAP -> {
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>) val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
HashSetSerializer(mapEntrySerial).serialize(this, (value as Map<*, *>).entries) SetSerializer(mapEntrySerial).serialize(this, (value as Map<*, *>).entries)
} }
serializer.descriptor == ByteArraySerializer.descriptor -> encoder.writeBytes(value as ByteArray, popTag().first) serializer.descriptor == ByteArraySerializer().descriptor -> encoder.writeBytes(
value as ByteArray,
popTag().first
)
else -> serializer.serialize(this, value) else -> serializer.serialize(this, value)
} }
} }
...@@ -96,7 +111,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac ...@@ -96,7 +111,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
val parentTag: ProtoDesc?, private val parentEncoder: ProtobufEncoder, val parentTag: ProtoDesc?, private val parentEncoder: ProtobufEncoder,
private val stream: ByteArrayOutputStream = ByteArrayOutputStream() private val stream: ByteArrayOutputStream = ByteArrayOutputStream()
) : ProtobufWriter(ProtobufEncoder(stream)) { ) : ProtobufWriter(ProtobufEncoder(stream)) {
override fun endEncode(desc: SerialDescriptor) { override fun endEncode(descriptor: SerialDescriptor) {
if (parentTag != null) { if (parentTag != null) {
parentEncoder.writeBytes(stream.toByteArray(), parentTag.first) parentEncoder.writeBytes(stream.toByteArray(), parentTag.first)
} else { } else {
...@@ -111,7 +126,8 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac ...@@ -111,7 +126,8 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
else 2 to (parentTag?.second ?: ProtoNumberType.DEFAULT) else 2 to (parentTag?.second ?: ProtoNumberType.DEFAULT)
} }
internal inner class RepeatedWriter(encoder: ProtobufEncoder, val curTag: ProtoDesc) : ProtobufWriter(encoder) { internal inner class RepeatedWriter(encoder: ProtobufEncoder, private val curTag: ProtoDesc) :
ProtobufWriter(encoder) {
override fun SerialDescriptor.getTag(index: Int) = curTag override fun SerialDescriptor.getTag(index: Int) = curTag
} }
...@@ -141,8 +157,9 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac ...@@ -141,8 +157,9 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
out.write(content) out.write(content)
} }
@OptIn(ExperimentalStdlibApi::class)
fun writeString(value: String, tag: Int) { fun writeString(value: String, tag: Int) {
val bytes = value.toUtf8Bytes() val bytes = value.encodeToByteArray()
writeBytes(bytes, tag) writeBytes(bytes, tag)
} }
...@@ -228,17 +245,17 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac ...@@ -228,17 +245,17 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
internal const val SIZE_DELIMITED = 2 internal const val SIZE_DELIMITED = 2
internal const val i32 = 5 internal const val i32 = 5
val plain = ProtoBufWithNullableSupport() private val plain = ProtoBufWithNullableSupport()
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray = plain.dump(serializer, obj) override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray = plain.dump(serializer, value)
override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T = plain.load(deserializer, bytes) override fun <T> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T =
override fun install(module: SerialModule) = throw IllegalStateException("You should not install anything to global instance") plain.load(deserializer, bytes)
} }
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray { override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
val encoder = ByteArrayOutputStream() val encoder = ByteArrayOutputStream()
val dumper = ProtobufWriter(ProtobufEncoder(encoder)) val dumper = ProtobufWriter(ProtobufEncoder(encoder))
dumper.encode(serializer, obj) dumper.encode(serializer, value)
return encoder.toByteArray() return encoder.toByteArray()
} }
...@@ -248,20 +265,3 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac ...@@ -248,20 +265,3 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
} }
internal fun InputStream.readExactNBytes(bytes: Int): ByteArray {
val array = ByteArray(bytes)
var read = 0
while (read < bytes) {
val i = this.read(array, read, bytes - read)
if (i == -1) throw IOException("Unexpected EOF")
read += i
}
return array
}
internal fun InputStream.readToByteBuffer(bytes: Int): ByteBuffer {
val arr = readExactNBytes(bytes)
val buf = ByteBuffer.allocate(bytes)
buf.put(arr).flip()
return buf
}
\ 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.io.serialization.jce
import kotlinx.io.core.*
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
import net.mamoe.mirai.utils.io.readString
/**
* 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 = 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
repeat(skipToHeadAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
} * 2) {
useHead { skipField(it.type) }
}
}
Jce.LIST -> { // list
repeat(skipToHeadAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
}) {
useHead { skipField(it.type) }
}
}
Jce.STRUCT_BEGIN -> {
fun skipToStructEnd() {
var head: JceHead
do {
head = nextHead()
skipField(head.type)
} while (head.type.toInt() != 11)
}
skipToStructEnd()
}
Jce.STRUCT_END, Jce.ZERO_TYPE -> {
}
Jce.SIMPLE_LIST -> {
val head = nextHead()
check(head.type.toInt() == 0) { "skipField with invalid type, type value: " + type + ", " + head.type }
this.input.discardExact(
skipToHeadAndUseIfPossibleOrFail(0) {
readJceIntValue(it)
}
)
}
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.type}")
}
}
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.type}")
}
}
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.type}")
}
}
fun readJceFloatValue(head: JceHead): Float {
return when (head.type) {
Jce.ZERO_TYPE -> 0f
Jce.FLOAT -> input.readFloat()
else -> error("type mismatch: ${head.type}")
}
}
@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.type}, 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.type}")
}
}
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.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.io.serialization.IOFormat
import net.mamoe.mirai.qqandroid.io.serialization.JceCharset
import net.mamoe.mirai.qqandroid.io.serialization.JceOld
import net.mamoe.mirai.utils.io.toReadPacket
/**
* Jce 数据结构序列化和反序列化器.
*
* @author Him188
*/
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.io.serialization.jce
import kotlinx.io.core.Output
import kotlinx.serialization.SerialInfo
/**
* 标注 JCE 序列化时使用的 ID
*/
@SerialInfo
@Target(AnnotationTarget.PROPERTY)
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()
fun JceHead.checkType(type: Byte) {
check(this.type == type) { "type mismatch. Expected $type, actual ${this.type}" }
}
@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 {
val typeString = 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")
}
return "JceHead(tag=$tag, type=$type($typeString))"
}
}
\ No newline at end of file
...@@ -12,30 +12,35 @@ ...@@ -12,30 +12,35 @@
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
import io.ktor.utils.io.core.BytePacketBuilder import kotlinx.io.core.*
import io.ktor.utils.io.core.ByteReadPacket
import io.ktor.utils.io.core.readBytes
import io.ktor.utils.io.core.writeFully
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.SerialDescriptor import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.SerializationStrategy
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion2
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3 import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestDataVersion3
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.firstValue import net.mamoe.mirai.utils.firstValue
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.readPacketExact
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmMultifileClass
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
fun <T : JceStruct> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T { fun <T : JceStruct> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, c: JceCharset = JceCharset.UTF8): T {
return Jce.byCharSet(c).load(deserializer, this) return Jce.byCharSet(c).load(deserializer, this.toReadPacket())
} }
fun <T : JceStruct> BytePacketBuilder.writeJceStruct(serializer: SerializationStrategy<T>, struct: T, charset: JceCharset = JceCharset.GBK) { fun <T : JceStruct> BytePacketBuilder.writeJceStruct(
this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct)) serializer: SerializationStrategy<T>,
struct: T,
charset: JceCharset = JceCharset.GBK
) {
Jce.byCharSet(charset).dumpTo(serializer, struct, this)
} }
fun <T : JceStruct> ByteReadPacket.readJceStruct( fun <T : JceStruct> ByteReadPacket.readJceStruct(
...@@ -43,7 +48,8 @@ fun <T : JceStruct> ByteReadPacket.readJceStruct( ...@@ -43,7 +48,8 @@ fun <T : JceStruct> ByteReadPacket.readJceStruct(
charset: JceCharset = JceCharset.UTF8, charset: JceCharset = JceCharset.UTF8,
length: Int = this.remaining.toInt() length: Int = this.remaining.toInt()
): T { ): T {
return Jce.byCharSet(charset).load(serializer, this, length) @OptIn(MiraiInternalAPI::class)
return Jce.byCharSet(charset).load(serializer, this.readPacketExact(length))
} }
/** /**
...@@ -73,18 +79,20 @@ fun <T : ProtoBuf> ByteReadPacket.decodeUniPacket(deserializer: DeserializationS ...@@ -73,18 +79,20 @@ fun <T : ProtoBuf> ByteReadPacket.decodeUniPacket(deserializer: DeserializationS
fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R {
val request = this.readJceStruct(RequestPacket.serializer()) val request = this.readJceStruct(RequestPacket.serializer())
return block(if (name == null) when (request.iVersion.toInt()) { return block(if (name == null) when (request.iVersion?.toInt() ?: 3) {
2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue() 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.firstValue().firstValue()
3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.firstValue() 3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.firstValue()
else -> error("unsupported version ${request.iVersion}") else -> error("unsupported version ${request.iVersion}")
} else when (request.iVersion.toInt()) { } else when (request.iVersion?.toInt() ?: 3) {
2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.getOrElse(name) { error("cannot find $name") }.firstValue() 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.getOrElse(name) { error("cannot find $name") }
.firstValue()
3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.getOrElse(name) { error("cannot find $name") } 3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.getOrElse(name) { error("cannot find $name") }
else -> error("unsupported version ${request.iVersion}") else -> error("unsupported version ${request.iVersion}")
}) })
} }
fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this) fun <T : JceStruct> T.toByteArray(serializer: SerializationStrategy<T>, c: JceCharset = JceCharset.GBK): ByteArray =
Jce.byCharSet(c).dump(serializer, this)
fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) { fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) {
this.writeFully(v.toByteArray(serializer)) this.writeFully(v.toByteArray(serializer))
......
...@@ -23,6 +23,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.toByteArray ...@@ -23,6 +23,7 @@ import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.OnlinePush
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
...@@ -40,7 +41,7 @@ internal class MessageSourceFromServer( ...@@ -40,7 +41,7 @@ internal class MessageSourceFromServer(
?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or ?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or
delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.and(0xFFFFFFFF) delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.and(0xFFFFFFFF)
override val toUin: Long get() = delegate.toUin override val toUin: Long get() = delegate.toUin // always 0
override suspend fun ensureSequenceIdAvailable() { override suspend fun ensureSequenceIdAvailable() {
// nothing to do // nothing to do
...@@ -118,38 +119,16 @@ internal class MessageSourceFromMsg( ...@@ -118,38 +119,16 @@ internal class MessageSourceFromMsg(
} }
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg { private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
val groupUin = Group.calculateGroupUinByGroupCode(groupId)
return ImMsgBody.SourceMsg( return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq), origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin, senderUin = delegate.msgHead.fromUin,
toUin = groupUin, toUin = 0,
flag = 1, flag = 1,
elems = delegate.msgBody.richText.elems, elems = delegate.msgBody.richText.elems,
type = 0, type = 0,
time = delegate.msgHead.msgTime, time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr( pbReserve = EMPTY_BYTE_ARRAY,
origUids = messageRandom.toLong() and 0xffFFffFF srcMsg = EMPTY_BYTE_ARRAY
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq
toUin = groupUin, // group
msgType = delegate.msgHead.msgType, // 82?
c2cCmd = delegate.msgHead.c2cCmd,
msgSeq = delegate.msgHead.msgSeq,
msgTime = delegate.msgHead.msgTime,
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
groupInfo = MsgComm.GroupInfo(groupCode = groupId),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems
)
)
).toByteArray(MsgComm.Msg.serializer())
) )
} }
...@@ -253,7 +232,7 @@ internal class MessageSourceFromSendFriend( ...@@ -253,7 +232,7 @@ internal class MessageSourceFromSendFriend(
val sequenceId: Int, val sequenceId: Int,
override val originalMessage: MessageChain override val originalMessage: MessageChain
) : MessageSourceFromSend() { ) : MessageSourceFromSend() {
@UseExperimental(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override val id: Long override val id: Long
get() = sequenceId.toLong().shl(32) or get() = sequenceId.toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF) messageRandom.toLong().and(0xFFFFFFFF)
...@@ -277,12 +256,12 @@ internal class MessageSourceFromSendGroup( ...@@ -277,12 +256,12 @@ internal class MessageSourceFromSendGroup(
) : MessageSourceFromSend() { ) : MessageSourceFromSend() {
private lateinit var sequenceIdDeferred: Deferred<Int> private lateinit var sequenceIdDeferred: Deferred<Int>
@UseExperimental(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
override val id: Long override val id: Long
get() = sequenceIdDeferred.getCompleted().toLong().shl(32) or get() = sequenceIdDeferred.getCompleted().toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF) messageRandom.toLong().and(0xFFFFFFFF)
@UseExperimental(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) { internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) {
sequenceIdDeferred = sequenceIdDeferred =
coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>( coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
......
...@@ -9,15 +9,14 @@ ...@@ -9,15 +9,14 @@
package net.mamoe.mirai.qqandroid.message package net.mamoe.mirai.qqandroid.message
import io.ktor.utils.io.core.* import kotlinx.io.core.*
import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.read
import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toByteArray
...@@ -219,7 +218,7 @@ private val atAllData = ImMsgBody.Elem( ...@@ -219,7 +218,7 @@ private val atAllData = ImMsgBody.Elem(
) )
) )
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgBody.Elem> { internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgBody.Elem> {
val elements = mutableListOf<ImMsgBody.Elem>() val elements = mutableListOf<ImMsgBody.Elem>()
...@@ -240,6 +239,25 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB ...@@ -240,6 +239,25 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
elements.add(ImMsgBody.Elem(text = it.toJceData())) elements.add(ImMsgBody.Elem(text = it.toJceData()))
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
} }
is LightApp -> elements.add(
ImMsgBody.Elem(
lightApp = ImMsgBody.LightAppElem(
data = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is RichMessage -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = when (it) {
is XmlMessage -> 60
is JsonMessage -> 1
else -> error("unsupported RichMessage")
},
template1 = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) is OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
...@@ -249,7 +267,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB ...@@ -249,7 +267,7 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
is QuoteReplyToSend -> { is QuoteReplyToSend -> {
if (forGroup) { if (forGroup) {
check(it is QuoteReplyToSend.ToGroup) { check(it is QuoteReplyToSend.ToGroup) {
"sending a quote to group suing QuoteReplyToSend.ToFriend" "sending a quote to group using QuoteReplyToSend.ToFriend"
} }
if (it.sender is Member) { if (it.sender is Member) {
transformOneMessage(it.createAt()) transformOneMessage(it.createAt())
...@@ -266,9 +284,10 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB ...@@ -266,9 +284,10 @@ internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgB
} }
this.forEach(::transformOneMessage) this.forEach(::transformOneMessage)
// if(this.any<QuoteReply>()){ if (this.any<RichMessage>()) {
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()))) // 08 09 78 00 A0 01 81 DC 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00
// } elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "08 09 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 98 03 00 A0 03 20 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 08 03 90 04 80 80 80 10 B8 04 00 C0 04 00".hexToBytes())))
} else elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes())))
return elements return elements
} }
...@@ -332,7 +351,7 @@ internal class OnlineFriendImageImpl( ...@@ -332,7 +351,7 @@ internal class OnlineFriendImageImpl(
} }
} }
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun MsgComm.Msg.toMessageChain(): MessageChain { internal fun MsgComm.Msg.toMessageChain(): MessageChain {
val elements = this.msgBody.richText.elems val elements = this.msgBody.richText.elems
...@@ -343,7 +362,7 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain { ...@@ -343,7 +362,7 @@ internal fun MsgComm.Msg.toMessageChain(): MessageChain {
} }
// These two functions are not identical, dont combine. // These two functions are not identical, dont combine.
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain { internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
val elements = this.elems!! val elements = this.elems!!
...@@ -368,7 +387,9 @@ private fun MessageChain.removeAtIfHasQuoteReply(): MessageChain = ...@@ -368,7 +387,9 @@ private fun MessageChain.removeAtIfHasQuoteReply(): MessageChain =
}.asMessageChain() }.asMessageChain()
} else this*/ } else this*/
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class, LowLevelAPI::class) @OptIn(
MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class, LowLevelAPI::class
)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) { internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) {
this.forEach { this.forEach {
when { when {
...@@ -395,6 +416,23 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde ...@@ -395,6 +416,23 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilde
} }
} }
} }
it.lightApp != null -> {
val content = MiraiPlatformUtils.unzip(it.lightApp.data, 1).encodeToString()
message.add(LightApp(content))
}
it.richMsg != null -> {
val content = MiraiPlatformUtils.unzip(it.richMsg.template1, 1).encodeToString()
when (it.richMsg.serviceId) {
1 -> message.add(JsonMessage(content))
60 -> message.add(XmlMessage(content))
else -> {
@Suppress("DEPRECATION")
MiraiLogger.debug {
"unknown richMsg.serviceId: ${it.richMsg.serviceId}, content=${it.richMsg.template1.contentToString()}, \ntryUnzip=${content}"
}
}
}
}
} }
} }
......
...@@ -9,12 +9,15 @@ ...@@ -9,12 +9,15 @@
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import io.ktor.utils.io.core.*
import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.AtomicRef
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.use
import net.mamoe.mirai.data.MultiPacket import net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.* import net.mamoe.mirai.event.*
...@@ -45,7 +48,7 @@ import kotlin.jvm.Volatile ...@@ -45,7 +48,7 @@ import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() { internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() {
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job]) override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
...@@ -67,14 +70,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -67,14 +70,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
_packetReceiverJob?.join() _packetReceiverJob?.join()
return this.launch(CoroutineName("Incoming Packet Receiver")) { return this.launch(CoroutineName("Incoming Packet Receiver")) {
while (channel.isOpen) { while (channel.isOpen && isActive) {
val rawInput = try { val rawInput = try {
channel.read() channel.read()
} catch (e: CancellationException) { } catch (e: CancellationException) {
return@launch return@launch
} catch (e: Throwable) { } catch (e: Throwable) {
if (this@QQAndroidBotNetworkHandler.isActive) { if (this@QQAndroidBotNetworkHandler.isActive) {
BotOfflineEvent.Dropped(bot, e).broadcast() bot.launch { BotOfflineEvent.Dropped(bot, e).broadcast() }
} }
return@launch return@launch
} }
...@@ -141,10 +144,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -141,10 +144,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
continue@mainloop continue@mainloop
} }
is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> { is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> {
var ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url) val ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url).orEmpty()
if (ticket == null) {
ticket = ""
}
response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect() response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
continue@mainloop continue@mainloop
} }
...@@ -183,7 +183,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -183,7 +183,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? = internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? =
LockFreeLinkedList() LockFreeLinkedList()
@UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class) @OptIn(MiraiExperimentalAPI::class, ExperimentalTime::class)
override suspend fun init(): Unit = coroutineScope { override suspend fun init(): Unit = coroutineScope {
check(bot.isActive) { "bot is dead therefore network can't init" } check(bot.isActive) { "bot is dead therefore network can't init" }
check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't init" } check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't init" }
...@@ -338,6 +338,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -338,6 +338,14 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
suspend fun doHeartBeat(): Exception? { suspend fun doHeartBeat(): Exception? {
val lastException: Exception? val lastException: Exception?
try { try {
kotlin.runCatching {
Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
retry = 2
)
return null
}
Heartbeat.Alive(bot.client) Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>( .sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis, timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
...@@ -372,10 +380,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler ...@@ -372,10 +380,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
* *
* @param input 一个完整的包的内容, 去掉开头的 int 包长度 * @param input 一个完整的包的内容, 去掉开头的 int 包长度
*/ */
@UseExperimental(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
fun parsePacketAsync(input: Input): Job { fun parsePacketAsync(input: Input): Job {
return this.launch(start = CoroutineStart.ATOMIC) { return this.launch(
input.use { parsePacket(it) } start = CoroutineStart.ATOMIC
) {
try {
input.use { parsePacket(it) }
} catch (e: Exception) {
// 傻逼协程吞异常
logger.error(e)
}
} }
} }
......
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import io.ktor.utils.io.core.*
import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.io.core.*
import net.mamoe.mirai.BotAccount import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.RawAccountIdUse import net.mamoe.mirai.RawAccountIdUse
import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.data.OnlineStatus
...@@ -40,7 +40,7 @@ import net.mamoe.mirai.utils.io.* ...@@ -40,7 +40,7 @@ import net.mamoe.mirai.utils.io.*
DOMAINS DOMAINS
Pskey: "openmobile.qq.com" Pskey: "openmobile.qq.com"
*/ */
@UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class) @OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class)
@PublishedApi @PublishedApi
internal open class QQAndroidClient( internal open class QQAndroidClient(
context: Context, context: Context,
...@@ -158,8 +158,8 @@ internal open class QQAndroidClient( ...@@ -158,8 +158,8 @@ internal open class QQAndroidClient(
*/ */
val uin: Long get() = _uin val uin: Long get() = _uin
@UseExperimental(RawAccountIdUse::class) @OptIn(RawAccountIdUse::class)
@Suppress("PropertyName") @Suppress("PropertyName", "DEPRECATION_ERROR")
internal var _uin: Long = bot.account.id internal var _uin: Long = bot.account.id
var t530: ByteArray? = null var t530: ByteArray? = null
...@@ -191,8 +191,9 @@ internal open class QQAndroidClient( ...@@ -191,8 +191,9 @@ internal open class QQAndroidClient(
lateinit var t104: ByteArray lateinit var t104: ByteArray
} }
@OptIn(MiraiInternalAPI::class)
internal fun generateTgtgtKey(guid: ByteArray): ByteArray = internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
md5(getRandomByteArray(16) + guid) MiraiPlatformUtils.md5(getRandomByteArray(16) + guid)
internal class ReserveUinInfo( internal class ReserveUinInfo(
...@@ -315,7 +316,7 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) : ...@@ -315,7 +316,7 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
internal typealias PSKeyMap = MutableMap<String, PSKey> internal typealias PSKeyMap = MutableMap<String, PSKey>
internal typealias Pt4TokenMap = MutableMap<String, Pt4Token> internal typealias Pt4TokenMap = MutableMap<String, Pt4Token>
internal inline fun Input.readUShortLVString(): String = io.ktor.utils.io.core.String(this.readUShortLVByteArray()) internal inline fun Input.readUShortLVString(): String = kotlinx.io.core.String(this.readUShortLVByteArray())
internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
......
...@@ -16,25 +16,27 @@ import io.ktor.http.HttpStatusCode ...@@ -16,25 +16,27 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol import io.ktor.http.URLProtocol
import io.ktor.http.content.OutgoingContent import io.ktor.http.content.OutgoingContent
import io.ktor.http.userAgent import io.ktor.http.userAgent
import io.ktor.utils.io.ByteReadChannel import io.ktor.utils.io.ByteWriteChannel
import io.ktor.utils.io.copyAndClose
import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.readAvailable
import io.ktor.utils.io.core.readInt
import io.ktor.utils.io.core.use
import io.ktor.utils.io.pool.useInstance
import kotlinx.coroutines.InternalCoroutinesApi import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream 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.io.pool.useInstance
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.copyAndClose
import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.ByteArrayPool
import net.mamoe.mirai.utils.io.PlatformSocket import net.mamoe.mirai.utils.io.PlatformSocket
import net.mamoe.mirai.utils.io.withUse import net.mamoe.mirai.utils.io.withUse
import kotlinx.serialization.InternalSerializationApi
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
internal suspend fun HttpClient.postImage( internal suspend fun HttpClient.postImage(
htcmd: String, htcmd: String,
...@@ -67,7 +69,7 @@ internal suspend fun HttpClient.postImage( ...@@ -67,7 +69,7 @@ internal suspend fun HttpClient.postImage(
override val contentType: ContentType = ContentType.Image.Any override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize override val contentLength: Long = inputSize
override suspend fun writeTo(channel: io.ktor.utils.io.ByteWriteChannel) { override suspend fun writeTo(channel: ByteWriteChannel) {
ByteArrayPool.useInstance { buffer: ByteArray -> ByteArrayPool.useInstance { buffer: ByteArray ->
when (imageInput) { when (imageInput) {
is Input -> { is Input -> {
...@@ -92,9 +94,9 @@ internal suspend fun HttpClient.postImage( ...@@ -92,9 +94,9 @@ internal suspend fun HttpClient.postImage(
} }
} == HttpStatusCode.OK } == HttpStatusCode.OK
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal object HighwayHelper { internal object HighwayHelper {
@UseExperimental(InternalCoroutinesApi::class) @OptIn(InternalCoroutinesApi::class)
suspend fun uploadImage( suspend fun uploadImage(
client: QQAndroidClient, client: QQAndroidClient,
serverIp: String, serverIp: String,
......
...@@ -11,19 +11,24 @@ ...@@ -11,19 +11,24 @@
package net.mamoe.mirai.qqandroid.network.highway package net.mamoe.mirai.qqandroid.network.highway
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream 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 net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.utils.MiraiPlatformUtils
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal fun createImageDataPacketSequence( // RequestDataTrans internal fun createImageDataPacketSequence( // RequestDataTrans
client: QQAndroidClient, client: QQAndroidClient,
command: String, command: String,
...@@ -73,7 +78,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans ...@@ -73,7 +78,7 @@ internal fun createImageDataPacketSequence( // RequestDataTrans
dataoffset = offset, dataoffset = offset,
filesize = dataSize.toLong(), filesize = dataSize.toLong(),
serviceticket = uKey, serviceticket = uKey,
md5 = net.mamoe.mirai.utils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize), md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
fileMd5 = fileMd5, fileMd5 = fileMd5,
flag = 0, flag = 0,
rtcode = 0 rtcode = 0
......
...@@ -9,139 +9,139 @@ ...@@ -9,139 +9,139 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
@Serializable @Serializable
internal class BigDataChannel( internal class BigDataChannel(
@SerialId(0) val vBigdataIplists: List<BigDataIpList>, @JceId(0) val vBigdataIplists: List<BigDataIpList>,
@SerialId(1) val sBigdataSigSession: ByteArray? = null, @JceId(1) val sBigdataSigSession: ByteArray? = null,
@SerialId(2) val sBigdataKeySession: ByteArray? = null, @JceId(2) val sBigdataKeySession: ByteArray? = null,
@SerialId(3) val uSigUin: Long? = null, @JceId(3) val uSigUin: Long? = null,
@SerialId(4) val iConnectFlag: Int? = 1, @JceId(4) val iConnectFlag: Int? = 1,
@SerialId(5) val vBigdataPbBuf: ByteArray? = null @JceId(5) val vBigdataPbBuf: ByteArray? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class BigDataIpInfo( internal class BigDataIpInfo(
@SerialId(0) val uType: Long, @JceId(0) val uType: Long,
@SerialId(1) val sIp: String = "", @JceId(1) val sIp: String = "",
@SerialId(2) val uPort: Long @JceId(2) val uPort: Long
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class BigDataIpList( internal class BigDataIpList(
@SerialId(0) val uServiceType: Long, @JceId(0) val uServiceType: Long,
@SerialId(1) val vIplist: List<BigDataIpInfo>, @JceId(1) val vIplist: List<BigDataIpInfo>,
@SerialId(2) val netSegConfs: List<NetSegConf>? = null, @JceId(2) val netSegConfs: List<NetSegConf>? = null,
@SerialId(3) val ufragmentSize: Long? = null @JceId(3) val ufragmentSize: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class ClientLogConfig( internal class ClientLogConfig(
@SerialId(1) val type: Int, @JceId(1) val type: Int,
@SerialId(2) val timeStart: TimeStamp? = null, @JceId(2) val timeStart: TimeStamp? = null,
@SerialId(3) val timeFinish: TimeStamp? = null, @JceId(3) val timeFinish: TimeStamp? = null,
@SerialId(4) val loglevel: Byte? = null, @JceId(4) val loglevel: Byte? = null,
@SerialId(5) val cookie: Int? = null, @JceId(5) val cookie: Int? = null,
@SerialId(6) val lseq: Long? = null @JceId(6) val lseq: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class DomainIpChannel( internal class DomainIpChannel(
@SerialId(0) val vDomainIplists: List<DomainIpList> @JceId(0) val vDomainIplists: List<DomainIpList>
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class DomainIpInfo( internal class DomainIpInfo(
@SerialId(1) val uIp: Int, @JceId(1) val uIp: Int,
@SerialId(2) val uPort: Int @JceId(2) val uPort: Int
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class DomainIpList( internal class DomainIpList(
@SerialId(0) val uDomainType: Int, @JceId(0) val uDomainType: Int,
@SerialId(1) val vIplist: List<DomainIpInfo> @JceId(1) val vIplist: List<DomainIpInfo>
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class FileStoragePushFSSvcList( internal class FileStoragePushFSSvcList(
@SerialId(0) val vUpLoadList: List<FileStorageServerListInfo>, @JceId(0) val vUpLoadList: List<FileStorageServerListInfo>,
@SerialId(1) val vPicDownLoadList: List<FileStorageServerListInfo>, @JceId(1) val vPicDownLoadList: List<FileStorageServerListInfo>,
@SerialId(2) val vGPicDownLoadList: List<FileStorageServerListInfo>? = null, @JceId(2) val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
@SerialId(3) val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null, @JceId(3) val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
@SerialId(4) val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null, @JceId(4) val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,
@SerialId(5) val bigDataChannel: BigDataChannel? = null, @JceId(5) val bigDataChannel: BigDataChannel? = null,
@SerialId(6) val vVipEmotionList: List<FileStorageServerListInfo>? = null, @JceId(6) val vVipEmotionList: List<FileStorageServerListInfo>? = null,
@SerialId(7) val vC2CPicDownList: List<FileStorageServerListInfo>? = null, @JceId(7) val vC2CPicDownList: List<FileStorageServerListInfo>? = null,
@SerialId(8) val fmtIPInfo: FmtIPInfo? = null, @JceId(8) val fmtIPInfo: FmtIPInfo? = null,
@SerialId(9) val domainIpChannel: DomainIpChannel? = null, @JceId(9) val domainIpChannel: DomainIpChannel? = null,
@SerialId(10) val pttlist: ByteArray? = null @JceId(10) val pttlist: ByteArray? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class FileStorageServerListInfo( internal class FileStorageServerListInfo(
@SerialId(1) val sIP: String = "", @JceId(1) val sIP: String = "",
@SerialId(2) val iPort: Int @JceId(2) val iPort: Int
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class FmtIPInfo( internal class FmtIPInfo(
@SerialId(0) val sGateIp: String = "", @JceId(0) val sGateIp: String = "",
@SerialId(1) val iGateIpOper: Long @JceId(1) val iGateIpOper: Long
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class NetSegConf( internal class NetSegConf(
@SerialId(0) val uint32NetType: Long? = null, @JceId(0) val uint32NetType: Long? = null,
@SerialId(1) val uint32Segsize: Long? = null, @JceId(1) val uint32Segsize: Long? = null,
@SerialId(2) val uint32Segnum: Long? = null, @JceId(2) val uint32Segnum: Long? = null,
@SerialId(3) val uint32Curconnnum: Long? = null @JceId(3) val uint32Curconnnum: Long? = null
) : JceStruct ) : JceStruct
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")
@Serializable @Serializable
internal data class PushReq( internal data class PushReq(
@SerialId(1) val type: Int, @JceId(1) val type: Int,
@SerialId(2) val jcebuf: ByteArray, @JceId(2) val jcebuf: ByteArray,
@SerialId(3) val seq: Long @JceId(3) val seq: Long
) : JceStruct, Packet ) : JceStruct, Packet
@Serializable @Serializable
internal class PushResp( internal class PushResp(
@SerialId(1) val type: Int, @JceId(1) val type: Int,
@SerialId(2) val seq: Long, @JceId(2) val seq: Long,
@SerialId(3) val jcebuf: ByteArray? = null @JceId(3) val jcebuf: ByteArray? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class SsoServerList( internal class SsoServerList(
@SerialId(1) val v2G3GList: List<SsoServerListInfo>, @JceId(1) val v2G3GList: List<SsoServerListInfo>,
@SerialId(3) val vWifiList: List<SsoServerListInfo>, @JceId(3) val vWifiList: List<SsoServerListInfo>,
@SerialId(4) val iReconnect: Int, @JceId(4) val iReconnect: Int,
@SerialId(5) val testSpeed: Byte? = null, @JceId(5) val testSpeed: Byte? = null,
@SerialId(6) val useNewList: Byte? = null, @JceId(6) val useNewList: Byte? = null,
@SerialId(7) val iMultiConn: Int? = 1, @JceId(7) val iMultiConn: Int? = 1,
@SerialId(8) val vHttp2g3glist: List<SsoServerListInfo>? = null, @JceId(8) val vHttp2g3glist: List<SsoServerListInfo>? = null,
@SerialId(9) val vHttpWifilist: List<SsoServerListInfo>? = null @JceId(9) val vHttpWifilist: List<SsoServerListInfo>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class SsoServerListInfo( internal class SsoServerListInfo(
@SerialId(1) val sIP: String = "", @JceId(1) val sIP: String = "",
@SerialId(2) val iPort: Int, @JceId(2) val iPort: Int,
@SerialId(3) val linkType: Byte, @JceId(3) val linkType: Byte,
@SerialId(4) val proxy: Byte, @JceId(4) val proxy: Byte,
@SerialId(5) val protocolType: Byte? = null, @JceId(5) val protocolType: Byte? = null,
@SerialId(6) val iTimeOut: Int? = 10 @JceId(6) val iTimeOut: Int? = 10
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class TimeStamp( internal class TimeStamp(
@SerialId(1) val year: Int, @JceId(1) val year: Int,
@SerialId(2) val month: Byte, @JceId(2) val month: Byte,
@SerialId(3) val day: Byte, @JceId(3) val day: Byte,
@SerialId(4) val hour: Byte @JceId(4) val hour: Byte
) : JceStruct ) : JceStruct
...@@ -9,72 +9,72 @@ ...@@ -9,72 +9,72 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")
@Serializable @Serializable
internal data class RequestPushNotify( internal data class RequestPushNotify(
@SerialId(0) val uin: Long? = 0L, @JceId(0) val uin: Long? = 0L,
@SerialId(1) val ctype: Byte = 0, @JceId(1) val ctype: Byte = 0,
@SerialId(2) val strService: String?, @JceId(2) val strService: String?,
@SerialId(3) val strCmd: String?, @JceId(3) val strCmd: String?,
@SerialId(4) val vNotifyCookie: ByteArray? = EMPTY_BYTE_ARRAY, @JceId(4) val vNotifyCookie: ByteArray? = EMPTY_BYTE_ARRAY,
@SerialId(5) val usMsgType: Int?, @JceId(5) val usMsgType: Int?,
@SerialId(6) val wUserActive: Int?, @JceId(6) val wUserActive: Int?,
@SerialId(7) val wGeneralFlag: Int?, @JceId(7) val wGeneralFlag: Int?,
@SerialId(8) val bindedUin: Long?, @JceId(8) val bindedUin: Long?,
@SerialId(9) val stMsgInfo: MsgInfo?, @JceId(9) val stMsgInfo: MsgInfo?,
@SerialId(10) val msgCtrlBuf: String?, @JceId(10) val msgCtrlBuf: String?,
@SerialId(11) val serverBuf: ByteArray?, @JceId(11) val serverBuf: ByteArray?,
@SerialId(12) val pingFlag: Long?, @JceId(12) val pingFlag: Long?,
@SerialId(13) val svrip: Int? @JceId(13) val svrip: Int?
) : JceStruct, Packet ) : JceStruct, Packet
@Serializable @Serializable
internal class MsgInfo( internal class MsgInfo(
@SerialId(0) val lFromUin: Long? = 0L, @JceId(0) val lFromUin: Long? = 0L,
@SerialId(1) val uMsgTime: Long? = 0L, @JceId(1) val uMsgTime: Long? = 0L,
@SerialId(2) val shMsgType: Short, @JceId(2) val shMsgType: Short,
@SerialId(3) val shMsgSeq: Short, @JceId(3) val shMsgSeq: Short,
@SerialId(4) val strMsg: String?, @JceId(4) val strMsg: String?,
@SerialId(5) val uRealMsgTime: Int?, @JceId(5) val uRealMsgTime: Int?,
@SerialId(6) val vMsg: ByteArray?, @JceId(6) val vMsg: ByteArray?,
@SerialId(7) val uAppShareID: Long?, @JceId(7) val uAppShareID: Long?,
@SerialId(8) val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY, @JceId(8) val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY,
@SerialId(9) val vAppShareCookie: ByteArray? = EMPTY_BYTE_ARRAY, @JceId(9) val vAppShareCookie: ByteArray? = EMPTY_BYTE_ARRAY,
@SerialId(10) val lMsgUid: Long?, @JceId(10) val lMsgUid: Long?,
@SerialId(11) val lLastChangeTime: Long?, @JceId(11) val lLastChangeTime: Long?,
@SerialId(12) val vCPicInfo: List<CPicInfo>?, @JceId(12) val vCPicInfo: List<CPicInfo>?,
@SerialId(13) val stShareData: ShareData?, @JceId(13) val stShareData: ShareData?,
@SerialId(14) val lFromInstId: Long?, @JceId(14) val lFromInstId: Long?,
@SerialId(15) val vRemarkOfSender: ByteArray?, @JceId(15) val vRemarkOfSender: ByteArray?,
@SerialId(16) val strFromMobile: String?, @JceId(16) val strFromMobile: String?,
@SerialId(17) val strFromName: String?, @JceId(17) val strFromName: String?,
@SerialId(18) val vNickName: List<String>?//, @JceId(18) val vNickName: List<String>?//,
//@SerialId(19) val stC2CTmpMsgHead: TempMsgHead? //@SerialId(19) val stC2CTmpMsgHead: TempMsgHead?
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class ShareData( internal class ShareData(
@SerialId(0) val pkgname: String = "", @JceId(0) val pkgname: String = "",
@SerialId(1) val msgtail: String = "", @JceId(1) val msgtail: String = "",
@SerialId(2) val picurl: String = "", @JceId(2) val picurl: String = "",
@SerialId(3) val url: String = "" @JceId(3) val url: String = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class TempMsgHead( internal class TempMsgHead(
@SerialId(0) val c2c_type: Int? = 0, @JceId(0) val c2c_type: Int? = 0,
@SerialId(1) val serviceType: Int? = 0 @JceId(1) val serviceType: Int? = 0
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class CPicInfo( internal class CPicInfo(
@SerialId(0) val vPath: ByteArray = EMPTY_BYTE_ARRAY, @JceId(0) val vPath: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(1) val vHost: ByteArray? = EMPTY_BYTE_ARRAY @JceId(1) val vHost: ByteArray? = EMPTY_BYTE_ARRAY
) : JceStruct ) : JceStruct
\ No newline at end of file
...@@ -9,38 +9,38 @@ ...@@ -9,38 +9,38 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
private val EMPTY_MAP = mapOf<String, String>() private val EMPTY_MAP = mapOf<String, String>()
@Serializable @Serializable
internal class RequestPacket( internal class RequestPacket(
@SerialId(1) val iVersion: Short = 3, @JceId(1) val iVersion: Short? = 3,
@SerialId(2) val cPacketType: Byte = 0, @JceId(2) val cPacketType: Byte = 0,
@SerialId(3) val iMessageType: Int = 0, @JceId(3) val iMessageType: Int = 0,
@SerialId(4) val iRequestId: Int, @JceId(4) val iRequestId: Int,
@SerialId(5) val sServantName: String = "", @JceId(5) val sServantName: String = "",
@SerialId(6) val sFuncName: String = "", @JceId(6) val sFuncName: String = "",
@SerialId(7) val sBuffer: ByteArray = EMPTY_BYTE_ARRAY, @JceId(7) val sBuffer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val iTimeout: Int? = 0, @JceId(8) val iTimeout: Int? = 0,
@SerialId(9) val context: Map<String, String>? = EMPTY_MAP, @JceId(9) val context: Map<String, String>? = EMPTY_MAP,
@SerialId(10) val status: Map<String, String>? = EMPTY_MAP @JceId(10) val status: Map<String, String>? = EMPTY_MAP
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class RequestDataVersion3( internal class RequestDataVersion3(
@SerialId(0) val map: Map<String, ByteArray> // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的 @JceId(0) val map: Map<String, ByteArray> // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class RequestDataVersion2( internal class RequestDataVersion2(
@SerialId(0) val map: Map<String, Map<String, ByteArray>> @JceId(0) val map: Map<String, Map<String, ByteArray>>
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class RequestDataStructSvcReqRegister( internal class RequestDataStructSvcReqRegister(
@SerialId(0) val struct: SvcReqRegister @JceId(0) val struct: SvcReqRegister
) : JceStruct ) : JceStruct
\ No newline at end of file
...@@ -9,14 +9,15 @@ ...@@ -9,14 +9,15 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
@Serializable @Serializable
internal class RequestPushForceOffline( internal class RequestPushForceOffline(
@SerialId(0) val uin: Long, @JceId(0) val uin: Long,
@SerialId(1) val title: String? = "", @JceId(1) val title: String? = "",
@SerialId(2) val tips: String? = "", @JceId(2) val tips: String? = "",
@SerialId(3) val sameDevice: Byte? = null @JceId(3) val sameDevice: Byte? = null
) : JceStruct ) : JceStruct
\ No newline at end of file
...@@ -9,47 +9,47 @@ ...@@ -9,47 +9,47 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
@Serializable @Serializable
internal class SvcReqRegister( internal class SvcReqRegister(
@SerialId(0) val lUin: Long = 0L, @JceId(0) val lUin: Long = 0L,
@SerialId(1) val lBid: Long = 0L, @JceId(1) val lBid: Long = 0L,
@SerialId(2) val cConnType: Byte = 0, @JceId(2) val cConnType: Byte = 0,
@SerialId(3) val sOther: String = "", @JceId(3) val sOther: String = "",
@SerialId(4) val iStatus: Int = 11, @JceId(4) val iStatus: Int = 11,
@SerialId(5) val bOnlinePush: Byte = 0, @JceId(5) val bOnlinePush: Byte = 0,
@SerialId(6) val bIsOnline: Byte = 0, @JceId(6) val bIsOnline: Byte = 0,
@SerialId(7) val bIsShowOnline: Byte = 0, @JceId(7) val bIsShowOnline: Byte = 0,
@SerialId(8) val bKikPC: Byte = 0, @JceId(8) val bKikPC: Byte = 0,
@SerialId(9) val bKikWeak: Byte = 0, @JceId(9) val bKikWeak: Byte = 0,
@SerialId(10) val timeStamp: Long = 0L, @JceId(10) val timeStamp: Long = 0L,
@SerialId(11) val iOSVersion: Long = 0L, @JceId(11) val iOSVersion: Long = 0L,
@SerialId(12) val cNetType: Byte = 0, @JceId(12) val cNetType: Byte = 0,
@SerialId(13) val sBuildVer: String? = "", @JceId(13) val sBuildVer: String? = "",
@SerialId(14) val bRegType: Byte = 0, @JceId(14) val bRegType: Byte = 0,
@SerialId(15) val vecDevParam: ByteArray? = null, @JceId(15) val vecDevParam: ByteArray? = null,
@SerialId(16) val vecGuid: ByteArray? = null, @JceId(16) val vecGuid: ByteArray? = null,
@SerialId(17) val iLocaleID: Int = 2052, @JceId(17) val iLocaleID: Int = 2052,
@SerialId(18) val bSlientPush: Byte = 0, @JceId(18) val bSlientPush: Byte = 0,
@SerialId(19) val strDevName: String? = null, @JceId(19) val strDevName: String? = null,
@SerialId(20) val strDevType: String? = null, @JceId(20) val strDevType: String? = null,
@SerialId(21) val strOSVer: String? = null, @JceId(21) val strOSVer: String? = null,
@SerialId(22) val bOpenPush: Byte = 1, @JceId(22) val bOpenPush: Byte = 1,
@SerialId(23) val iLargeSeq: Long = 0L, @JceId(23) val iLargeSeq: Long = 0L,
@SerialId(24) val iLastWatchStartTime: Long = 0L, @JceId(24) val iLastWatchStartTime: Long = 0L,
@SerialId(26) val uOldSSOIp: Long = 0L, @JceId(26) val uOldSSOIp: Long = 0L,
@SerialId(27) val uNewSSOIp: Long = 0L, @JceId(27) val uNewSSOIp: Long = 0L,
@SerialId(28) val sChannelNo: String? = null, @JceId(28) val sChannelNo: String? = null,
@SerialId(29) val lCpId: Long = 0L, @JceId(29) val lCpId: Long = 0L,
@SerialId(30) val strVendorName: String? = null, @JceId(30) val strVendorName: String? = null,
@SerialId(31) val strVendorOSName: String? = null, @JceId(31) val strVendorOSName: String? = null,
@SerialId(32) val strIOSIdfa: String? = null, @JceId(32) val strIOSIdfa: String? = null,
@SerialId(33) val bytes_0x769_reqbody: ByteArray? = null, @JceId(33) val bytes_0x769_reqbody: ByteArray? = null,
@SerialId(34) val bIsSetStatus: Byte = 0, @JceId(34) val bIsSetStatus: Byte = 0,
@SerialId(35) val vecServerBuf: ByteArray? = null, @JceId(35) val vecServerBuf: ByteArray? = null,
@SerialId(36) val bSetMute: Byte = 0 @JceId(36) val bSetMute: Byte = 0
// @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型 // @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型
) : JceStruct ) : JceStruct
\ No newline at end of file
...@@ -11,8 +11,8 @@ ...@@ -11,8 +11,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import kotlinx.serialization.protobuf.ProtoNumberType import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
...@@ -22,74 +22,74 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY ...@@ -22,74 +22,74 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
class Oidb0x858 : ProtoBuf { class Oidb0x858 : ProtoBuf {
@Serializable @Serializable
class GoldMsgTipsElem( class GoldMsgTipsElem(
@SerialId(1) val type: Int = 0, @ProtoId(1) val type: Int = 0,
@SerialId(2) val billno: String = "", @ProtoId(2) val billno: String = "",
@SerialId(3) val result: Int = 0, @ProtoId(3) val result: Int = 0,
@SerialId(4) val amount: Int = 0, @ProtoId(4) val amount: Int = 0,
@SerialId(5) val total: Int = 0, @ProtoId(5) val total: Int = 0,
@SerialId(6) val interval: Int = 0, @ProtoId(6) val interval: Int = 0,
@SerialId(7) val finish: Int = 0, @ProtoId(7) val finish: Int = 0,
@SerialId(8) val uin: List<Long>? = null, @ProtoId(8) val uin: List<Long>? = null,
@SerialId(9) val action: Int = 0 @ProtoId(9) val action: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class MessageRecallReminder( class MessageRecallReminder(
@SerialId(1) val uin: Long = 0L, @ProtoId(1) val uin: Long = 0L,
@SerialId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val recalledMsgList: List<MessageMeta> = listOf(), @ProtoId(3) val recalledMsgList: List<MessageMeta> = listOf(),
@SerialId(4) val reminderContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val reminderContent: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class MessageMeta( class MessageMeta(
@SerialId(1) val seq: Int = 0, @ProtoId(1) val seq: Int = 0,
@SerialId(2) val time: Int = 0, @ProtoId(2) val time: Int = 0,
@SerialId(3) val msgRandom: Int = 0 @ProtoId(3) val msgRandom: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@Serializable @Serializable
class NotifyMsgBody( class NotifyMsgBody(
@SerialId(1) val optEnumType: Int /* enum */ = 5, @ProtoId(1) val optEnumType: Int /* enum */ = 5,
@SerialId(2) val optUint64MsgTime: Long = 0L, @ProtoId(2) val optUint64MsgTime: Long = 0L,
@SerialId(3) val optUint64MsgExpires: Long = 0L, @ProtoId(3) val optUint64MsgExpires: Long = 0L,
@SerialId(4) val optUint64ConfUin: Long = 0L, @ProtoId(4) val optUint64ConfUin: Long = 0L,
@SerialId(5) val optMsgRedtips: RedGrayTipsInfo? = null, @ProtoId(5) val optMsgRedtips: RedGrayTipsInfo? = null,
@SerialId(6) val optMsgRecallReminder: MessageRecallReminder? = null, @ProtoId(6) val optMsgRecallReminder: MessageRecallReminder? = null,
@SerialId(7) val optMsgObjUpdate: NotifyObjmsgUpdate? = null, @ProtoId(7) val optMsgObjUpdate: NotifyObjmsgUpdate? = null,
// @SerialId(8) val optStcmGameState: ApolloGameStatus.STCMGameMessage? = null, // @SerialId(8) val optStcmGameState: ApolloGameStatus.STCMGameMessage? = null,
// @SerialId(9) val aplloMsgPush: ApolloPushMsgInfo.STPushMsgElem? = null, // @SerialId(9) val aplloMsgPush: ApolloPushMsgInfo.STPushMsgElem? = null,
@SerialId(10) val optMsgGoldtips: GoldMsgTipsElem? = null @ProtoId(10) val optMsgGoldtips: GoldMsgTipsElem? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class NotifyObjmsgUpdate( class NotifyObjmsgUpdate(
@SerialId(1) val objmsgId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val objmsgId: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val updateType: Int = 0, @ProtoId(2) val updateType: Int = 0,
@SerialId(3) val extMsg: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(3) val extMsg: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RedGrayTipsInfo( class RedGrayTipsInfo(
@SerialId(1) val optUint32ShowLastest: Int = 0, @ProtoId(1) val optUint32ShowLastest: Int = 0,
@SerialId(2) val senderUin: Long = 0L, @ProtoId(2) val senderUin: Long = 0L,
@SerialId(3) val receiverUin: Long = 0L, @ProtoId(3) val receiverUin: Long = 0L,
@SerialId(4) val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(5) val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val authkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(6) val authkey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoType(ProtoNumberType.SIGNED) @SerialId(7) val sint32Msgtype: Int = 0, @ProtoType(ProtoNumberType.SIGNED) @ProtoId(7) val sint32Msgtype: Int = 0,
@SerialId(8) val luckyFlag: Int = 0, @ProtoId(8) val luckyFlag: Int = 0,
@SerialId(9) val hideFlag: Int = 0, @ProtoId(9) val hideFlag: Int = 0,
@SerialId(10) val pcBody: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(10) val pcBody: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(11) val icon: Int = 0, @ProtoId(11) val icon: Int = 0,
@SerialId(12) val luckyUin: Long = 0L, @ProtoId(12) val luckyUin: Long = 0L,
@SerialId(13) val time: Int = 0, @ProtoId(13) val time: Int = 0,
@SerialId(14) val random: Int = 0, @ProtoId(14) val random: Int = 0,
@SerialId(15) val broadcastRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(15) val broadcastRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(16) val idiom: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(16) val idiom: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(17) val idiomSeq: Int = 0, @ProtoId(17) val idiomSeq: Int = 0,
@SerialId(18) val idiomAlpha: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(18) val idiomAlpha: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(19) val jumpurl: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(19) val jumpurl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
} }
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
...@@ -18,40 +18,40 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY ...@@ -18,40 +18,40 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
internal class Vec0xd50 : ProtoBuf { internal class Vec0xd50 : ProtoBuf {
@Serializable @Serializable
internal class ExtSnsFrdData( internal class ExtSnsFrdData(
@SerialId(1) val frdUin: Long = 0L, @ProtoId(1) val frdUin: Long = 0L,
@SerialId(91001) val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(91001) val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(101001) val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(101001) val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(141001) val mutualmarkScore: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(141001) val mutualmarkScore: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(151001) val ksingSwitch: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(151001) val ksingSwitch: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(181001) val lbsShare: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(181001) val lbsShare: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class RspBody( internal class RspBody(
@SerialId(1) val msgUpdateData: List<Vec0xd50.ExtSnsFrdData>? = null, @ProtoId(1) val msgUpdateData: List<Vec0xd50.ExtSnsFrdData>? = null,
@SerialId(11) val over: Int = 0, @ProtoId(11) val over: Int = 0,
@SerialId(12) val nextStart: Int = 0, @ProtoId(12) val nextStart: Int = 0,
@SerialId(13) val uint64UnfinishedUins: List<Long>? = null @ProtoId(13) val uint64UnfinishedUins: List<Long>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ReqBody( internal class ReqBody(
@SerialId(1) val appid: Long = 0L, @ProtoId(1) val appid: Long = 0L,
@SerialId(2) val maxPkgSize: Int = 0, @ProtoId(2) val maxPkgSize: Int = 0,
@SerialId(3) val startTime: Int = 0, @ProtoId(3) val startTime: Int = 0,
@SerialId(4) val startIndex: Int = 0, @ProtoId(4) val startIndex: Int = 0,
@SerialId(5) val reqNum: Int = 0, @ProtoId(5) val reqNum: Int = 0,
@SerialId(6) val uinList: List<Long>? = null, @ProtoId(6) val uinList: List<Long>? = null,
@SerialId(91001) val reqMusicSwitch: Int = 0, @ProtoId(91001) val reqMusicSwitch: Int = 0,
@SerialId(101001) val reqMutualmarkAlienation: Int = 0, @ProtoId(101001) val reqMutualmarkAlienation: Int = 0,
@SerialId(141001) val reqMutualmarkScore: Int = 0, @ProtoId(141001) val reqMutualmarkScore: Int = 0,
@SerialId(151001) val reqKsingSwitch: Int = 0, @ProtoId(151001) val reqKsingSwitch: Int = 0,
@SerialId(181001) val reqMutualmarkLbsshare: Int = 0 @ProtoId(181001) val reqMutualmarkLbsshare: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class KSingRelationInfo( internal class KSingRelationInfo(
@SerialId(1) val flag: Int = 0 @ProtoId(1) val flag: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
...@@ -59,21 +59,21 @@ internal class Vec0xd50 : ProtoBuf { ...@@ -59,21 +59,21 @@ internal class Vec0xd50 : ProtoBuf {
internal class Vec0xd6b : ProtoBuf { internal class Vec0xd6b : ProtoBuf {
@Serializable @Serializable
internal class ReqBody( internal class ReqBody(
@SerialId(1) val maxPkgSize: Int = 0, @ProtoId(1) val maxPkgSize: Int = 0,
@SerialId(2) val startTime: Int = 0, @ProtoId(2) val startTime: Int = 0,
@SerialId(11) val uinList: List<Long>? = null @ProtoId(11) val uinList: List<Long>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class RspBody( internal class RspBody(
@SerialId(11) val msgMutualmarkData: List<Vec0xd6b.MutualMarkData>? = null, @ProtoId(11) val msgMutualmarkData: List<Vec0xd6b.MutualMarkData>? = null,
@SerialId(12) val uint64UnfinishedUins: List<Long>? = null @ProtoId(12) val uint64UnfinishedUins: List<Long>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class MutualMarkData( internal class MutualMarkData(
@SerialId(1) val frdUin: Long = 0L, @ProtoId(1) val frdUin: Long = 0L,
@SerialId(2) val result: Int = 0 @ProtoId(2) val result: Int = 0
// @SerialId(11) val mutualmarkInfo: List<Mutualmark.MutualMark>? = null // @SerialId(11) val mutualmarkInfo: List<Mutualmark.MutualMark>? = null
) : ProtoBuf ) : ProtoBuf
} }
...@@ -82,26 +82,26 @@ internal class Vec0xd6b : ProtoBuf { ...@@ -82,26 +82,26 @@ internal class Vec0xd6b : ProtoBuf {
internal class Mutualmark : ProtoBuf { internal class Mutualmark : ProtoBuf {
@Serializable @Serializable
internal class MutualmarkInfo( internal class MutualmarkInfo(
@SerialId(1) val lastActionTime: Long = 0L, @ProtoId(1) val lastActionTime: Long = 0L,
@SerialId(2) val level: Int = 0, @ProtoId(2) val level: Int = 0,
@SerialId(3) val lastChangeTime: Long = 0L, @ProtoId(3) val lastChangeTime: Long = 0L,
@SerialId(4) val continueDays: Int = 0, @ProtoId(4) val continueDays: Int = 0,
@SerialId(5) val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(5) val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val notifyTime: Long = 0L, @ProtoId(6) val notifyTime: Long = 0L,
@SerialId(7) val iconStatus: Long = 0L, @ProtoId(7) val iconStatus: Long = 0L,
@SerialId(8) val iconStatusEndTime: Long = 0L, @ProtoId(8) val iconStatusEndTime: Long = 0L,
@SerialId(9) val closeFlag: Int = 0, @ProtoId(9) val closeFlag: Int = 0,
@SerialId(10) val resourceInfo: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(10) val resourceInfo: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ResourceInfo17( internal class ResourceInfo17(
@SerialId(1) val dynamicUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val dynamicUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val staticUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val staticUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val cartoonUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val cartoonUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val cartoonMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val cartoonMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val playCartoon: Int = 0, @ProtoId(5) val playCartoon: Int = 0,
@SerialId(6) val word: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(6) val word: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
} }
......
...@@ -9,8 +9,8 @@ ...@@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
...@@ -20,22 +20,22 @@ interface ImgReq : ProtoBuf ...@@ -20,22 +20,22 @@ interface ImgReq : ProtoBuf
@Serializable @Serializable
internal class GetImgUrlReq( internal class GetImgUrlReq(
@SerialId(1) val srcUni: Int, @ProtoId(1) val srcUni: Int,
@SerialId(2) val dstUni: Int, @ProtoId(2) val dstUni: Int,
@SerialId(3) val fileResID: String,//UUID @ProtoId(3) val fileResID: String,//UUID
/** /**
* UUID例子: 没有找到 * UUID例子: 没有找到
*/ */
@SerialId(4) val urlFlag: Int = 1, @ProtoId(4) val urlFlag: Int = 1,
//5 unknown, 好像没用 //5 unknown, 好像没用
@SerialId(6) val urlType: Int = 4, @ProtoId(6) val urlType: Int = 4,
@SerialId(7) val requestTerm: Int = 5,//确定 @ProtoId(7) val requestTerm: Int = 5,//确定
@SerialId(8) val requestPlatformType: Int = 9,//确定 @ProtoId(8) val requestPlatformType: Int = 9,//确定
@SerialId(9) val srcFileType: Int = 1,//2=ftn,1=picplatform,255 @ProtoId(9) val srcFileType: Int = 1,//2=ftn,1=picplatform,255
@SerialId(10) val innerIP: Int = 0,//确定 @ProtoId(10) val innerIP: Int = 0,//确定
@SerialId(11) val addressBook: Int = 0,//[ChatType.internalID]== 1006为1[为CONTACT时] 我觉得发0没问题 @ProtoId(11) val addressBook: Int = 0,//[ChatType.internalID]== 1006为1[为CONTACT时] 我觉得发0没问题
@SerialId(12) val buType: Int = 1,//确定 @ProtoId(12) val buType: Int = 1,//确定
@SerialId(13) val buildVer: String = "8.2.7.4410",//版本号 @ProtoId(13) val buildVer: String = "8.2.7.4410",//版本号
@SerialId(14) val timestamp: Int = currentTimeSeconds.toInt(),//(pic_up_timestamp) @ProtoId(14) val timestamp: Int = currentTimeSeconds.toInt(),//(pic_up_timestamp)
@SerialId(15) val requestTransferType: Int = 1 @ProtoId(15) val requestTransferType: Int = 1
) : ImgReq ) : ImgReq
\ No newline at end of file
...@@ -9,14 +9,17 @@ ...@@ -9,14 +9,17 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet package net.mamoe.mirai.qqandroid.network.protocol.packet
import io.ktor.utils.io.core.* import kotlinx.io.core.BytePacketBuilder
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.ECDHKeyPair import net.mamoe.mirai.utils.cryptor.ECDHKeyPair
import net.mamoe.mirai.utils.io.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeShortLVByteArray import net.mamoe.mirai.utils.io.writeShortLVByteArray
@UseExperimental(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
internal interface EncryptMethod { internal interface EncryptMethod {
val id: Int val id: Int
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
package net.mamoe.mirai.qqandroid.network.protocol.packet.chat package net.mamoe.mirai.qqandroid.network.protocol.packet.chat
import io.ktor.utils.io.core.ByteReadPacket import kotlinx.io.core.ByteReadPacket
import net.mamoe.mirai.data.Packet import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.QQAndroidBot import net.mamoe.mirai.qqandroid.QQAndroidBot
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
......
This diff is collapsed.
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