Merge remote-tracking branch 'origin/master'

# Conflicts:
#	mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt
This commit is contained in:
jiahua.liu 2020-02-08 21:44:24 +08:00
commit 571b4c4162
27 changed files with 568 additions and 412 deletions

View File

@ -300,13 +300,13 @@ fun main() {
```json5 ```json5
{ {
"type": "Face", "type": "Face",
"faceID": 123 "faceId": 123
} }
``` ```
| 名字 | 类型 | 说明 | | 名字 | 类型 | 说明 |
| ------ | ---- | ---------- | | ------ | ---- | ---------- |
| faceID | Int | QQ表情编号 | | faceId | Int | QQ表情编号 |
#### Plain #### Plain
@ -453,7 +453,7 @@ fun main() {
### 群全体禁言 ### 群全体禁言
使用此方法令指定群进行全体禁言 使用此方法令指定群进行全体禁言(需要有相关限权)
``` ```
[POST] /muteAll [POST] /muteAll
@ -487,7 +487,7 @@ fun main() {
### 群解除全体禁言 ### 群解除全体禁言
使用此方法令指定群解除全体禁言 使用此方法令指定群解除全体禁言(需要有相关限权)
``` ```
[POST] /unmuteAll [POST] /unmuteAll
@ -505,7 +505,7 @@ fun main() {
### 群禁言群成员 ### 群禁言群成员
使用此方法指定群禁言指定群员 使用此方法指定群禁言指定群员(需要有相关限权)
``` ```
[POST] /mute [POST] /mute
@ -517,7 +517,7 @@ fun main() {
{ {
"sessionKey": "YourSessionKey", "sessionKey": "YourSessionKey",
"target": 123456789, "target": 123456789,
"member": 987654321, "memberId": 987654321,
"time": 1800 "time": 1800
} }
``` ```
@ -526,7 +526,7 @@ fun main() {
| ---------- | ----- | ------ | ---------------- | ------------------------------------- | | ---------- | ----- | ------ | ---------------- | ------------------------------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key | | sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 | | target | false | Long | 123456789 | 指定群的群号 |
| member | false | Long | 987654321 | 指定群员QQ号 | | memberId | false | Long | 987654321 | 指定群员QQ号 |
| time | true | Int | 1800 | 禁言时长单位为秒最多30天默认为0 | | time | true | Int | 1800 | 禁言时长单位为秒最多30天默认为0 |
#### 响应: 返回统一状态码 #### 响应: 返回统一状态码
@ -542,7 +542,7 @@ fun main() {
### 群解除群成员禁言 ### 群解除群成员禁言
使用此方法令指定群解除全体禁言 使用此方法令指定群解除全体禁言(需要有相关限权)
``` ```
[POST] /unmute [POST] /unmute
@ -554,7 +554,7 @@ fun main() {
{ {
"sessionKey": "YourSessionKey", "sessionKey": "YourSessionKey",
"target": 123456789, "target": 123456789,
"member": 987654321 "memberId": 987654321
} }
``` ```
@ -564,9 +564,48 @@ fun main() {
### 移除群成员
使用此方法移除指定群成员(需要有相关限权)
```
[POST] /kick
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321,
"msg": "您已被移出群聊"
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ---------- | ----- | ------ | ---------------- | --------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 指定群员QQ号 |
| msg | true | String | "" | 信息 |
#### 响应
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 群设置 ### 群设置
使用此方法修改群设置(需要又相关限权) 使用此方法修改群设置(需要相关限权)
``` ```
[POST] /groupConfig [POST] /groupConfig
@ -638,3 +677,70 @@ fun main() {
"anonymousChat": true "anonymousChat": true
} }
``` ```
### 修改群员资料
使用此方法修改群员资料(需要有相关限权)
```
[POST] /memberInfo
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321,
"info": {
"name": "群名片",
"specialTitle": "群头衔"
}
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 群员QQ号 |
| info | false | Object | {} | 群员资料 |
| name | true | String | "Name" | 群名片,即群昵称 |
| specialTitle | true | String | "Title" | 群头衔 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 获取群员资料
使用此方法获取群员资料
```
[Get] /groupConfig?sessionKey=YourSessionKey&target=123456789
```
#### 请求:
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | YourSessionKey | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 群员QQ号 |
#### 响应
```json5
{
"name": "群名片",
"announcement": "群头衔"
}
```

View File

@ -0,0 +1,38 @@
package net.mamoe.mirai.api.http.data
/**
* 错误请求. 抛出这个异常后将会返回错误给一个请求
*/
@Suppress("unused")
open class IllegalAccessException : Exception {
override val message: String get() = super.message!!
constructor(message: String) : super(message, null)
constructor(cause: Throwable) : super(cause.toString(), cause)
constructor(message: String, cause: Throwable?) : super(message, cause)
}
/**
* Session失效或不存在
*/
object IllegalSessionException : IllegalAccessException("Session失效或不存在")
/**
* Session未激活
*/
object NotVerifiedSessionException : IllegalAccessException("Session未激活")
/**
* 指定Bot不存在
*/
object NoSuchBotException: IllegalAccessException("指定Bot不存在")
/**
* 指定Bot不存在
*/
object PermissionDeniedException: IllegalAccessException("无操作限权")
/**
* 错误参数
*/
class IllegalParamException(message: String) : IllegalAccessException(message)

View File

@ -0,0 +1,21 @@
package net.mamoe.mirai.api.http.data
import kotlinx.serialization.Serializable
@Serializable
open class StateCode(val code: Int, var msg: String) {
object Success : StateCode(0, "success") // 成功
object NoBot : StateCode(2, "指定Bot不存在")
object IllegalSession : StateCode(3, "Session失效或不存在")
object NotVerifySession : StateCode(4, "Session未认证")
object NoElement : StateCode(5, "指定对象不存在")
object PermissionDenied : StateCode(10, "无操作权限")
// KS bug: 主构造器中不能有非字段参数 https://github.com/Kotlin/kotlinx.serialization/issues/575
@Serializable
class IllegalAccess() : StateCode(400, "") { // 非法访问
constructor(msg: String) : this() {
this.msg = msg
}
}
}

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.api.http.dto package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
@ -30,7 +30,8 @@ data class MemberDTO(
val group: GroupDTO val group: GroupDTO
) : ContactDTO() { ) : ContactDTO() {
constructor(member: Member) : this ( constructor(member: Member) : this (
member.id, member.groupCard, member.permission, GroupDTO(member.group) member.id, member.groupCard, member.permission,
GroupDTO(member.group)
) )
} }

View File

@ -0,0 +1,18 @@
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import net.mamoe.mirai.api.http.AuthedSession
interface DTO
@Serializable
data class AuthDTO(val authKey: String) : DTO
@Serializable
abstract class VerifyDTO : DTO {
abstract val sessionKey: String
@Transient
lateinit var session: AuthedSession // 反序列化验证成功后传入
}

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.api.http.dto package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,6 +0,0 @@
package net.mamoe.mirai.api.http.dto
import kotlinx.serialization.Serializable
@Serializable
data class AuthDTO(val authKey: String) : DTO

View File

@ -1,97 +0,0 @@
package net.mamoe.mirai.api.http.dto
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
@Serializable
abstract class VerifyDTO : DTO {
abstract val sessionKey: String
@Transient
lateinit var session: AuthedSession // 反序列化验证后传入
}
@Serializable
data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO()
@Serializable
data class SendDTO(
override val sessionKey: String,
val target: Long,
val messageChain: MessageChainDTO
) : VerifyDTO()
typealias GroupTargetDTO = FriendTargetDTO
@Serializable
data class FriendTargetDTO(
override val sessionKey: String,
val target: Long
) : VerifyDTO()
@Serializable
data class MuteDTO(
override val sessionKey: String,
val target: Long,
val member: Long = 0,
val time: Int = 0
) : VerifyDTO()
@Serializable
data class GroupConfigDTO(
override val sessionKey: String,
val target: Long,
val config: GroupInfoDTO
) : VerifyDTO()
@Serializable
data class GroupInfoDTO(
val name: String? = null,
val announcement: String? = null,
val confessTalk: Boolean? = null,
val allowMemberInvite: Boolean? = null,
val autoApprove: Boolean? = null,
val anonymousChat: Boolean? = null
) : DTO {
constructor(group: Group) : this(
group.name, group.announcement, group.confessTalk, group.allowMemberInvite,
group.autoApprove, group.anonymousChat
)
}
@Serializable
data class MemberConfigDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long,
val config: MemberInfoDTO
) : VerifyDTO()
@Serializable
data class MemberInfoDTO(
val name: String? = null,
val specialTitle: String? = null
) : DTO {
constructor(member: Member) : this(member.groupCard, member.specialTitle)
}
@Serializable
open class StateCode(val code: Int, var msg: String) {
object Success : StateCode(0, "success") // 成功
object NoBot : StateCode(2, "指定Bot不存在")
object IllegalSession : StateCode(3, "Session失效或不存在")
object NotVerifySession : StateCode(4, "Session未认证")
object NoElement : StateCode(5, "指定对象不存在")
object PermissionDenied : StateCode(10, "无操作权限")
// KS bug: 主构造器中不能有非字段参数 https://github.com/Kotlin/kotlinx.serialization/issues/575
@Serializable
class IllegalAccess() : StateCode(400, "") { // 非法访问
constructor(msg: String) : this() {
this.msg = msg
}
}
}

View File

@ -3,10 +3,12 @@ package net.mamoe.mirai.api.http.route
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.call import io.ktor.application.call
import io.ktor.routing.routing import io.ktor.routing.routing
import kotlinx.serialization.Serializable
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.api.http.AuthedSession import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.dto.* import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@ -43,6 +45,9 @@ fun Application.authModule() {
} }
} }
@Serializable
private data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO()
private fun getBotOrThrow(qq: Long) = try { private fun getBotOrThrow(qq: Long) = try {
Bot.instanceWhose(qq) Bot.instanceWhose(qq)
} catch (e: NoSuchElementException) { } catch (e: NoSuchElementException) {

View File

@ -19,7 +19,10 @@ import io.ktor.util.pipeline.PipelineContext
import net.mamoe.mirai.api.http.AuthedSession import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.TempSession import net.mamoe.mirai.api.http.TempSession
import net.mamoe.mirai.api.http.dto.* import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.*
import net.mamoe.mirai.api.http.util.jsonParseOrNull
import net.mamoe.mirai.api.http.util.toJson
fun Application.mirai() { fun Application.mirai() {
install(DefaultHeaders) install(DefaultHeaders)
@ -136,14 +139,13 @@ internal suspend fun ApplicationCall.respondJson(json: String, status: HttpStatu
internal suspend inline fun <reified T : DTO> ApplicationCall.receiveDTO(): T? = receive<String>().jsonParseOrNull() internal suspend inline fun <reified T : DTO> ApplicationCall.receiveDTO(): T? = receive<String>().jsonParseOrNull()
fun PipelineContext<Unit, ApplicationCall>.illegalParam( fun PipelineContext<Unit, ApplicationCall>.illegalParam(
expectingType: String?, expectingType: String?,
paramName: String, paramName: String,
actualValue: String? = call.parameters[paramName] actualValue: String? = call.parameters[paramName]
): Nothing = throw IllegalParamException("Illegal param. A $expectingType is required for `$paramName` while `$actualValue` is given") ): Nothing = throw IllegalParamException("Illegal param. A $expectingType is required for `$paramName` while `$actualValue` is given")
@Suppress("IMPLICIT_CAST_TO_ANY") @Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class) @UseExperimental(ExperimentalUnsignedTypes::class)
internal inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R = internal inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R =
@ -171,42 +173,3 @@ internal inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNu
else -> error(name::class.simpleName + " is not supported") else -> error(name::class.simpleName + " is not supported")
} as R ?: illegalParam(R::class.simpleName, name) } as R ?: illegalParam(R::class.simpleName, name)
/**
* 错误请求. 抛出这个异常后将会返回错误给一个请求
*/
@Suppress("unused")
open class IllegalAccessException : Exception {
override val message: String get() = super.message!!
constructor(message: String) : super(message, null)
constructor(cause: Throwable) : super(cause.toString(), cause)
constructor(message: String, cause: Throwable?) : super(message, cause)
}
/**
* Session失效或不存在
*/
object IllegalSessionException : IllegalAccessException("Session失效或不存在")
/**
* Session未激活
*/
object NotVerifiedSessionException : IllegalAccessException("Session未激活")
/**
* 指定Bot不存在
*/
object NoSuchBotException: IllegalAccessException("指定Bot不存在")
/**
* 指定Bot不存在
*/
object PermissionDeniedException: IllegalAccessException("无操作限权")
/**
* 错误参数
*/
class IllegalParamException(message: String) : IllegalAccessException(message)

View File

@ -1,79 +0,0 @@
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import net.mamoe.mirai.api.http.dto.*
fun Application.groupManageModule() {
routing {
/**
* 禁言需要相关权限
*/
miraiVerify<MuteDTO>("/muteAll") {
it.session.bot.getGroup(it.target).muteAll = true
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/unmuteAll") {
it.session.bot.getGroup(it.target).muteAll = false
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/mute") {
when(it.session.bot.getGroup(it.target)[it.member].mute(it.time)) {
true -> call.respondStateCode(StateCode.Success)
else -> throw PermissionDeniedException
}
}
miraiVerify<MuteDTO>("/unmute") {
when(it.session.bot.getGroup(it.target).members[it.member].unmute()) {
true -> call.respondStateCode(StateCode.Success)
else -> throw PermissionDeniedException
}
}
/**
* 群设置需要相关权限
*/
miraiGet("/groupConfig") {
val group = it.bot.getGroup(paramOrNull("target"))
call.respondDTO(GroupInfoDTO(group))
}
miraiVerify<GroupConfigDTO>("/groupConfig") { dto ->
val group = dto.session.bot.getGroup(dto.target)
with(dto.config) {
name?.let { group.name = it }
announcement?.let { group.announcement = it }
confessTalk?.let { group.confessTalk = it }
allowMemberInvite?.let{ group.allowMemberInvite = it }
// TODO: 待core接口实现设置可改
// autoApprove?.let { group.autoApprove = it }
// anonymousChat?.let { group.anonymousChat = it }
}
call.respondStateCode(StateCode.Success)
}
/**
* 群员信息管理需要相关权限
*/
miraiGet("/memberInfo") {
val member = it.bot.getGroup(paramOrNull("target"))[paramOrNull("memberID")]
call.respondDTO(MemberInfoDTO(member))
}
miraiVerify<MemberConfigDTO>("/memberInfo") { dto ->
val member = dto.session.bot.getGroup(dto.target)[dto.memberId]
with(dto.config) {
name?.let { member.groupCard = it }
specialTitle?.let { member.specialTitle = it }
}
call.respondStateCode(StateCode.Success)
}
}
}

View File

@ -0,0 +1,149 @@
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import kotlinx.serialization.Serializable
import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.DTO
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
fun Application.groupManageModule() {
routing {
/**
* 禁言需要相关权限
*/
miraiVerify<MuteDTO>("/muteAll") {
it.session.bot.getGroup(it.target).muteAll = true
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/unmuteAll") {
it.session.bot.getGroup(it.target).muteAll = false
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/mute") {
when (it.session.bot.getGroup(it.target)[it.memberId].mute(it.time)) {
true -> call.respondStateCode(StateCode.Success)
else -> throw PermissionDeniedException
}
}
miraiVerify<MuteDTO>("/unmute") {
when (it.session.bot.getGroup(it.target).members[it.memberId].unmute()) {
true -> call.respondStateCode(StateCode.Success)
else -> throw PermissionDeniedException
}
}
/**
* 移出群聊需要相关权限
*/
miraiVerify<KickDTO>("/kick") {
when (it.session.bot.getGroup(it.target)[it.memberId].kick(it.msg)) {
true -> call.respondStateCode(StateCode.Success)
else -> throw PermissionDeniedException
}
}
/**
* 群设置需要相关权限
*/
miraiGet("/groupConfig") {
val group = it.bot.getGroup(paramOrNull("target"))
call.respondDTO(GroupDetailDTO(group))
}
miraiVerify<GroupConfigDTO>("/groupConfig") { dto ->
val group = dto.session.bot.getGroup(dto.target)
with(dto.config) {
name?.let { group.name = it }
announcement?.let { group.announcement = it }
confessTalk?.let { group.confessTalk = it }
allowMemberInvite?.let { group.allowMemberInvite = it }
// TODO: 待core接口实现设置可改
// autoApprove?.let { group.autoApprove = it }
// anonymousChat?.let { group.anonymousChat = it }
}
call.respondStateCode(StateCode.Success)
}
/**
* 群员信息管理需要相关权限
*/
miraiGet("/memberInfo") {
val member = it.bot.getGroup(paramOrNull("target"))[paramOrNull("memberId")]
call.respondDTO(MemberDetailDTO(member))
}
miraiVerify<MemberInfoDTO>("/memberInfo") { dto ->
val member = dto.session.bot.getGroup(dto.target)[dto.memberId]
with(dto.info) {
name?.let { member.groupCard = it }
specialTitle?.let { member.specialTitle = it }
}
call.respondStateCode(StateCode.Success)
}
}
}
@Serializable
private data class MuteDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long = 0,
val time: Int = 0
) : VerifyDTO()
@Serializable
private data class KickDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long,
val msg: String = ""
) : VerifyDTO()
@Serializable
private data class GroupConfigDTO(
override val sessionKey: String,
val target: Long,
val config: GroupDetailDTO
) : VerifyDTO()
@Serializable
private data class GroupDetailDTO(
val name: String? = null,
val announcement: String? = null,
val confessTalk: Boolean? = null,
val allowMemberInvite: Boolean? = null,
val autoApprove: Boolean? = null,
val anonymousChat: Boolean? = null
) : DTO {
constructor(group: Group) : this(
group.name, group.announcement, group.confessTalk, group.allowMemberInvite,
group.autoApprove, group.anonymousChat
)
}
@Serializable
private data class MemberInfoDTO(
override val sessionKey: String,
val target: Long,
val memberId: Long,
val info: MemberDetailDTO
) : VerifyDTO()
@Serializable
private data class MemberDetailDTO(
val name: String? = null,
val specialTitle: String? = null
) : DTO {
constructor(member: Member) : this(member.groupCard, member.specialTitle)
}

View File

@ -3,10 +3,10 @@ package net.mamoe.mirai.api.http.route
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.call import io.ktor.application.call
import io.ktor.routing.routing import io.ktor.routing.routing
import net.mamoe.mirai.api.http.dto.GroupDTO import net.mamoe.mirai.api.http.data.common.GroupDTO
import net.mamoe.mirai.api.http.dto.MemberDTO import net.mamoe.mirai.api.http.data.common.MemberDTO
import net.mamoe.mirai.api.http.dto.QQDTO import net.mamoe.mirai.api.http.data.common.QQDTO
import net.mamoe.mirai.api.http.dto.toJson import net.mamoe.mirai.api.http.util.toJson
import net.mamoe.mirai.contact.toMutableList import net.mamoe.mirai.contact.toMutableList
fun Application.infoModule() { fun Application.infoModule() {

View File

@ -3,7 +3,10 @@ package net.mamoe.mirai.api.http.route
import io.ktor.application.Application import io.ktor.application.Application
import io.ktor.application.call import io.ktor.application.call
import io.ktor.routing.routing import io.ktor.routing.routing
import net.mamoe.mirai.api.http.dto.* import kotlinx.serialization.Serializable
import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.*
import net.mamoe.mirai.api.http.util.toJson
fun Application.messageModule() { fun Application.messageModule() {
routing { routing {
@ -26,12 +29,12 @@ fun Application.messageModule() {
call.respondStateCode(StateCode.Success) call.respondStateCode(StateCode.Success)
} }
miraiVerify<VerifyDTO>("/event/message") {
}
miraiVerify<VerifyDTO>("/addFriend") {
}
} }
} }
@Serializable
private data class SendDTO(
override val sessionKey: String,
val target: Long,
val messageChain: MessageChainDTO
) : VerifyDTO()

View File

@ -1,10 +1,9 @@
package net.mamoe.mirai.api.http.dto package net.mamoe.mirai.api.http.util
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import net.mamoe.mirai.api.http.data.common.*
interface DTO
// 解析失败时直接返回null由路由判断响应400状态 // 解析失败时直接返回null由路由判断响应400状态
@UseExperimental(ImplicitReflectionSerializer::class) @UseExperimental(ImplicitReflectionSerializer::class)
@ -20,7 +19,7 @@ inline fun <reified T : Any> String.jsonParseOrNull(
inline fun <reified T : Any> T.toJson( inline fun <reified T : Any> T.toJson(
serializer: SerializationStrategy<T>? = null serializer: SerializationStrategy<T>? = null
): String = if (serializer == null) MiraiJson.json.stringify(this) ): String = if (serializer == null) MiraiJson.json.stringify(this)
else MiraiJson.json.stringify(serializer, this) else MiraiJson.json.stringify(serializer, this)
// 序列化列表时stringify需要使用的泛型是T而非List<T> // 序列化列表时stringify需要使用的泛型是T而非List<T>

View File

@ -1,4 +1,4 @@
package net.mamoe.mirai.qqandroid.utils package net.mamoe.mirai.qqandroid.message
import kotlinx.io.core.readUInt import kotlinx.io.core.readUInt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*

View File

@ -33,6 +33,8 @@ import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.Volatile import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime
import kotlin.time.measureTime
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@UseExperimental(MiraiInternalAPI::class) @UseExperimental(MiraiInternalAPI::class)
@ -106,8 +108,8 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>(6000) // it's slow StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>(6000) // it's slow
} }
@UseExperimental(MiraiExperimentalAPI::class) @UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class)
override suspend fun init() { override suspend fun init(): Unit = coroutineScope {
this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> { this@QQAndroidBotNetworkHandler.subscribeAlways<ForceOfflineEvent> {
if (this@QQAndroidBotNetworkHandler.bot == this.bot) { if (this@QQAndroidBotNetworkHandler.bot == this.bot) {
this.bot.logger.error("被挤下线") this.bot.logger.error("被挤下线")
@ -117,102 +119,106 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect() MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
//val msg = MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendAndExpect<MessageSvc.PbGetMsg.Response>()
//println(msg.contentToString())
bot.qqs.delegate.clear() bot.qqs.delegate.clear()
bot.groups.delegate.clear() bot.groups.delegate.clear()
val startTime = currentTimeMillis val friendListLoadTime = async {
try { measureTime {
bot.logger.info("开始加载好友信息") try {
var currentFriendCount = 0 bot.logger.info("开始加载好友信息")
var totalFriendCount: Short var currentFriendCount = 0
while (true) { var totalFriendCount: Short
val data = FriendList.GetFriendGroupList( while (true) {
bot.client, val data = FriendList.GetFriendGroupList(
currentFriendCount, bot.client,
150, currentFriendCount,
0, 150,
0 0,
).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 5000, retry = 2) 0
).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 5000, retry = 2)
totalFriendCount = data.totalFriendCount totalFriendCount = data.totalFriendCount
data.friendList.forEach { data.friendList.forEach {
// atomic add // atomic add
bot.qqs.delegate.addLast(QQImpl(bot, bot.coroutineContext, it.friendUin)).also { bot.qqs.delegate.addLast(QQImpl(bot, bot.coroutineContext, it.friendUin)).also {
currentFriendCount++ currentFriendCount++
}
}
bot.logger.verbose("正在加载好友列表 ${currentFriendCount}/${totalFriendCount}")
if (currentFriendCount >= totalFriendCount) {
break
}
// delay(200)
} }
bot.logger.info("好友列表加载完成, 共 ${currentFriendCount}")
} catch (e: Exception) {
bot.logger.error("加载好友列表失败|一般这是由于加载过于频繁导致/将以热加载方式加载好友列表")
} }
bot.logger.verbose("正在加载好友列表 ${currentFriendCount}/${totalFriendCount}")
if (currentFriendCount >= totalFriendCount) {
break
}
// delay(200)
} }
bot.logger.info("好友列表加载完成, 共 ${currentFriendCount}")
} catch (e: Exception) {
bot.logger.error("加载好友列表失败|一般这是由于加载过于频繁导致/将以热加载方式加载好友列表")
} }
val friendLoadFinish = currentTimeMillis
val groupInfo = mutableMapOf<Long, Int>() val groupInfo = mutableMapOf<Long, Int>()
coroutineScope {
try { val groupTime = async {
bot.logger.info("开始加载群组列表与群成员列表") measureTime {
val troopListData = FriendList.GetTroopListSimplify(bot.client) try {
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 5000, retry = 2) bot.logger.info("开始加载群组列表与群成员列表")
// println("获取到群数量" + troopData.groups.size) val troopListData = FriendList.GetTroopListSimplify(bot.client)
val toGet: MutableMap<GroupImpl, ContactList<Member>> = mutableMapOf() .sendAndExpect<FriendList.GetTroopListSimplify.Response>(timeoutMillis = 5000, retry = 2)
troopListData.groups.forEach { troopNum -> // println("获取到群数量" + troopData.groups.size)
val contactList = ContactList(LockFreeLinkedList<Member>()) val toGet: MutableMap<GroupImpl, ContactList<Member>> = mutableMapOf()
val groupInfoResponse = try { troopListData.groups.forEach { troopNum ->
TroopManagement.GetGroupOperationInfo( val contactList = ContactList(LockFreeLinkedList<Member>())
client = bot.client, val groupInfoResponse = try {
groupCode = troopNum.groupCode TroopManagement.GetGroupOperationInfo(
).sendAndExpect<TroopManagement.GetGroupOperationInfo.Response>() client = bot.client,
} catch (e: Exception) { groupCode = troopNum.groupCode
bot.logger.info("获取" + troopNum.groupCode + "的群设置失败") ).sendAndExpect<TroopManagement.GetGroupOperationInfo.Response>()
TroopManagement.GetGroupOperationInfo.Response(
allowAnonymousChat = false,
allowMemberInvite = false,
autoApprove = false,
confessTalk = false
)
}
val group =
GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
uin = troopNum.groupUin,
_name = troopNum.groupName,
_announcement = troopNum.groupMemo,
_allowMemberInvite = groupInfoResponse.allowMemberInvite,
_confessTalk = groupInfoResponse.confessTalk,
_muteAll = troopNum.dwShutUpTimestamp != 0L,
_autoApprove = groupInfoResponse.autoApprove,
_anonymousChat = groupInfoResponse.allowAnonymousChat,
members = contactList
)
toGet[group] = contactList
bot.groups.delegate.addLast(group)
launch {
try {
getTroopMemberList(group, contactList, troopNum.dwGroupOwnerUin)
groupInfo[troopNum.groupCode] = contactList.size
} catch (e: Exception) { } catch (e: Exception) {
groupInfo[troopNum.groupCode] = -1 bot.logger.info("获取" + troopNum.groupCode + "的群设置失败")
bot.logger.info("${troopNum.groupCode}的列表拉取失败, 将采用动态加入") TroopManagement.GetGroupOperationInfo.Response(
bot.logger.error(e) allowAnonymousChat = false,
allowMemberInvite = false,
autoApprove = false,
confessTalk = false
)
}
val group =
GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
uin = troopNum.groupUin,
_name = troopNum.groupName,
_announcement = troopNum.groupMemo,
_allowMemberInvite = groupInfoResponse.allowMemberInvite,
_confessTalk = groupInfoResponse.confessTalk,
_muteAll = troopNum.dwShutUpTimestamp != 0L,
_autoApprove = groupInfoResponse.autoApprove,
_anonymousChat = groupInfoResponse.allowAnonymousChat,
members = contactList
)
toGet[group] = contactList
bot.groups.delegate.addLast(group)
launch {
try {
fillTroopMemberList(group, contactList, troopNum.dwGroupOwnerUin)
groupInfo[troopNum.groupCode] = contactList.size
} catch (e: Exception) {
groupInfo[troopNum.groupCode] = -1
bot.logger.warning("${troopNum.groupCode}的列表拉取失败, 将采用动态加入")
bot.logger.error(e)
}
} }
} }
bot.logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}")
} catch (e: Exception) {
bot.logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
bot.logger.error(e)
} }
bot.logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}")
} catch (e: Exception) {
bot.logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
bot.logger.error(e)
} }
} }
//===log===// //===log===//
fun fillUntil(long: Number, size: Int): String { fun fillUntil(long: Number, size: Int): String {
val x = long.toString() val x = long.toString()
@ -225,9 +231,11 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
) )
} }
joinAll(friendListLoadTime, groupTime)
bot.logger.info("====================Mirai Bot List初始化完毕====================") bot.logger.info("====================Mirai Bot List初始化完毕====================")
bot.logger.info("好友数量: ${fillUntil(bot.qqs.size, 9)}\t\t\t 加载时间: ${friendLoadFinish - startTime}ms") bot.logger.info("好友数量: ${fillUntil(bot.qqs.size, 9)}\t\t\t 加载时间: ${friendListLoadTime.await().inMilliseconds}ms")
bot.logger.info("加入群组: ${fillUntil(bot.groups.size, 9)}\t\t\t 加载时间: ${currentTimeMillis - friendLoadFinish}ms") bot.logger.info("加入群组: ${fillUntil(bot.groups.size, 9)}\t\t\t 加载时间: ${groupTime.await().inMilliseconds}ms")
groupInfo.forEach { groupInfo.forEach {
if (it.value == -1) { if (it.value == -1) {
bot.logger.error("群组号码: ${fillUntil(it.key, 9)}\t 成员数量加载失败") bot.logger.error("群组号码: ${fillUntil(it.key, 9)}\t 成员数量加载失败")
@ -242,34 +250,40 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
bot.logger.info("====================Mirai Bot List初始化完毕====================") bot.logger.info("====================Mirai Bot List初始化完毕====================")
bot.firstLoginSucceed = true this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) {
launch {
while (this.isActive) { while (this.isActive) {
delay(bot.configuration.heartbeatPeriodMillis) delay(bot.configuration.heartbeatPeriodMillis)
var lastException: Exception? val failException = null//doHeartBeat()
try { if (failException != null) {
check( delay(bot.configuration.firstReconnectDelayMillis)
StatSvc.GetOnlineStatus(bot.client) close()
.sendAndExpect<StatSvc.GetOnlineStatus.Response>( bot.tryReinitializeNetworkHandler(failException)
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
retry = 1
) is StatSvc.GetOnlineStatus.Response.Success
)
continue
} catch (e: Exception) {
lastException = e
} }
delay(bot.configuration.firstReconnectDelayMillis)
close()
bot.tryReinitializeNetworkHandler(lastException)
} }
} }
bot.firstLoginSucceed = true
} }
suspend fun doHeartBeat(): Exception? {
var lastException: Exception?
try {
check(
StatSvc.GetOnlineStatus(bot.client)
.sendAndExpect<StatSvc.GetOnlineStatus.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
retry = 1
) is StatSvc.GetOnlineStatus.Response.Success
)
return null
} catch (e: Exception) {
lastException = e
}
return lastException
}
suspend fun getTroopMemberList(group: GroupImpl, list: ContactList<Member>, owner: Long): ContactList<Member> { suspend fun fillTroopMemberList(group: GroupImpl, list: ContactList<Member>, owner: Long) {
bot.logger.info("开始获取群[${group.uin}]成员列表") bot.logger.verbose("开始获取群[${group.uin}]成员列表")
var size = 0 var size = 0
var nextUin = 0L var nextUin = 0L
while (true) { while (true) {
@ -306,10 +320,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
if (nextUin == 0L) { if (nextUin == 0L) {
break break
} }
//println("已获取群[${group.uin}]成员列表前" + size + "个成员")
} }
//println("群[${group.uin}]成员全部获取完成, 共${list.size}个成员")
return list
} }
/** /**
@ -473,7 +484,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
cachedPacketTimeoutJob = launch { cachedPacketTimeoutJob = launch {
delay(1000) delay(1000)
if (cachedPacketTimeoutJob == this.coroutineContext[Job] && cachedPacket.getAndSet(null) != null) { if (cachedPacketTimeoutJob == this.coroutineContext[Job] && cachedPacket.getAndSet(null) != null) {
PacketLogger.verbose("等待另一部分包时超时. 将舍弃已接收的半个包") PacketLogger.verbose { "等待另一部分包时超时. 将舍弃已接收的半个包" }
} }
} }
} }

View File

@ -64,14 +64,14 @@ internal open class QQAndroidClient(
internal inline fun <R> tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? { internal inline fun <R> tryDecryptOrNull(data: ByteArray, size: Int = data.size, mapper: (ByteArray) -> R): R? {
keys.forEach { (key, value) -> keys.forEach { (key, value) ->
kotlin.runCatching { kotlin.runCatching {
return mapper(data.decryptBy(value, size).also { PacketLogger.verbose("成功使用 $key 解密") }) return mapper(data.decryptBy(value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } })
} }
} }
return null return null
} }
override fun toString(): String { // net.mamoe.mirai.utils.cryptor.ProtoKt.contentToString override fun toString(): String { // extremely slow
return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.contentToString()}, randomKey=${randomKey.contentToString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, openAppId=$openAppId, apkVersionName=${apkVersionName.contentToString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.contentToString()}, protocolVersion=$protocolVersion, apkId=${apkId.contentToString()}, t150=${t150?.contentToString()}, rollbackSig=${rollbackSig?.contentToString()}, ipFromT149=${ipFromT149?.contentToString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.contentToString()}, t528=${t528?.contentToString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.contentToString()}, qrPushSig=${qrPushSig.contentToString()}, mainDisplayName='$mainDisplayName')" return "QQAndroidClient(account=$account, ecdh=$ecdh, device=$device, tgtgtKey=${tgtgtKey.toUHexString()}, randomKey=${randomKey.toUHexString()}, miscBitMap=$miscBitMap, mainSigMap=$mainSigMap, subSigMap=$subSigMap, openAppId=$openAppId, apkVersionName=${apkVersionName.toUHexString()}, loginState=$loginState, appClientVersion=$appClientVersion, networkType=$networkType, apkSignatureMd5=${apkSignatureMd5.toUHexString()}, protocolVersion=$protocolVersion, apkId=${apkId.toUHexString()}, t150=${t150?.value?.toUHexString()}, rollbackSig=${rollbackSig?.toUHexString()}, ipFromT149=${ipFromT149?.toUHexString()}, timeDifference=$timeDifference, uin=$uin, t530=${t530?.toUHexString()}, t528=${t528?.toUHexString()}, ksid='$ksid', pwdFlag=$pwdFlag, loginExtraData=$loginExtraData, wFastLoginInfo=$wFastLoginInfo, reserveUinInfo=$reserveUinInfo, wLoginSigInfo=$wLoginSigInfo, tlv113=${tlv113?.toUHexString()}, qrPushSig=${qrPushSig.toUHexString()}, mainDisplayName='$mainDisplayName')"
} }
var onlineStatus: OnlineStatus = OnlineStatus.ONLINE var onlineStatus: OnlineStatus = OnlineStatus.ONLINE
@ -86,8 +86,6 @@ internal open class QQAndroidClient(
var mainSigMap: Int = 16724722 var mainSigMap: Int = 16724722
var subSigMap: Int = 0x10400 //=66,560 var subSigMap: Int = 0x10400 //=66,560
var configPushSvcPushReqSequenceId: Int = 0
private val _ssoSequenceId: AtomicInt = atomic(85600) private val _ssoSequenceId: AtomicInt = atomic(85600)
@MiraiInternalAPI("Do not use directly. Get from the lambda param of buildSsoPacket") @MiraiInternalAPI("Do not use directly. Get from the lambda param of buildSsoPacket")
@ -136,7 +134,7 @@ internal open class QQAndroidClient(
@PublishedApi @PublishedApi
internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray() internal val apkId: ByteArray = "com.tencent.mobileqq".toByteArray()
var outgoingPacketUnknownValue: ByteArray = 0x02B05B8B.toByteArray() var outgoingPacketSessionId: ByteArray = 0x02B05B8B.toByteArray()
var loginState = 0 var loginState = 0
var t150: Tlv? = null var t150: Tlv? = null
@ -194,7 +192,7 @@ internal class ReserveUinInfo(
val imgUrl: ByteArray val imgUrl: ByteArray
) { ) {
override fun toString(): String { override fun toString(): String {
return "ReserveUinInfo(imgType=${imgType.contentToString()}, imgFormat=${imgFormat.contentToString()}, imgUrl=${imgUrl.contentToString()})" return "ReserveUinInfo(imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()})"
} }
} }
@ -222,7 +220,7 @@ internal class WLoginSimpleInfo(
val mainDisplayName: ByteArray val mainDisplayName: ByteArray
) { ) {
override fun toString(): String { override fun toString(): String {
return "WLoginSimpleInfo(uin=$uin, face=$face, age=$age, gender=$gender, nick='$nick', imgType=${imgType.contentToString()}, imgFormat=${imgFormat.contentToString()}, imgUrl=${imgUrl.contentToString()}, mainDisplayName=${mainDisplayName.contentToString()})" return "WLoginSimpleInfo(uin=$uin, face=$face, age=$age, gender=$gender, nick='$nick', imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()}, mainDisplayName=${mainDisplayName.toUHexString()})"
} }
} }
@ -233,7 +231,7 @@ internal class LoginExtraData(
val version: Int val version: Int
) { ) {
override fun toString(): String { override fun toString(): String {
return "LoginExtraData(uin=$uin, ip=${ip.contentToString()}, time=$time, version=$version)" return "LoginExtraData(uin=$uin, ip=${ip.toUHexString()}, time=$time, version=$version)"
} }
} }
@ -285,7 +283,7 @@ internal class WLoginSigInfo(
val deviceToken: ByteArray val deviceToken: ByteArray
) { ) {
override fun toString(): String { override fun toString(): String {
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1.contentToString()}, noPicSig=${noPicSig.contentToString()}, G=${G.contentToString()}, dpwd=${dpwd.contentToString()}, randSeed=${randSeed.contentToString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.contentToString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.contentToString()}, userStSig=$userStSig, userStKey=${userStKey.contentToString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.contentToString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.contentToString()}, sid=$sid, aqSig=$aqSig, psKey=${psKeyMap.contentToString()}, superKey=${superKey.contentToString()}, payToken=${payToken.contentToString()}, pf=${pf.contentToString()}, pfKey=${pfKey.contentToString()}, da2=${da2.contentToString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.contentToString()}, deviceToken=${deviceToken.contentToString()})" return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, G=${G.toUHexString()}, dpwd=${dpwd.toUHexString()}, randSeed=${randSeed.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=${psKeyMap.toString()}, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
} }
} }
@ -330,9 +328,17 @@ internal open class KeyWithExpiry(
data: ByteArray, data: ByteArray,
creationTime: Long, creationTime: Long,
val expireTime: Long val expireTime: Long
) : KeyWithCreationTime(data, creationTime) ) : KeyWithCreationTime(data, creationTime) {
override fun toString(): String {
return "KeyWithExpiry(data=${data.toUHexString()}, creationTime=$creationTime)"
}
}
internal open class KeyWithCreationTime( internal open class KeyWithCreationTime(
val data: ByteArray, val data: ByteArray,
val creationTime: Long val creationTime: Long
) ) {
override fun toString(): String {
return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)"
}
}

View File

@ -80,7 +80,7 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
writeStringUtf8(it) writeStringUtf8(it)
} }
encryptAndWrite(key) { encryptAndWrite(key) {
writeUniPacket(commandName, client.outgoingPacketUnknownValue, extraData) { writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) {
body(sequenceId) body(sequenceId)
} }
} }
@ -111,7 +111,7 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
writeStringUtf8(it) writeStringUtf8(it)
} }
encryptAndWrite(key) { encryptAndWrite(key) {
writeUniPacket(commandName, client.outgoingPacketUnknownValue, extraData) { writeUniPacket(commandName, client.outgoingPacketSessionId, extraData) {
body(sequenceId) body(sequenceId)
} }
} }
@ -215,7 +215,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
} }
writeInt(4 + 4) writeInt(4 + 4)
writeFully(client.outgoingPacketUnknownValue) // 02 B0 5B 8B writeFully(client.outgoingPacketSessionId) // 02 B0 5B 8B
client.device.imei.let { client.device.imei.let {
writeInt(it.length + 4) writeInt(it.length + 4)

View File

@ -162,15 +162,15 @@ internal object KnownPacketFactories {
// login // login
val flag1 = readInt() val flag1 = readInt()
PacketLogger.verbose("开始处理一个包") PacketLogger.verbose { "开始处理一个包" }
PacketLogger.verbose("flag1(0A/0B) = ${flag1.toUByte().toUHexString()}") PacketLogger.verbose { "flag1(0A/0B) = ${flag1.toUByte().toUHexString()}" }
// 00 00 05 30 // 00 00 05 30
// 00 00 00 0A // flag 1 // 00 00 00 0A // flag 1
// 01 // packet type. 02: sso, 01: uni // 01 // packet type. 02: sso, 01: uni
// //
// 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 40 3C 63 DC A2 8F FC E7 09 66 62 11 A3 5A B6 AB DC 6E A1 CA CF E2 0A 6F A8 6D 36 64 4E 22 4B A9 8A ED 07 7A 0A 9E F3 C7 7B 72 EF C1 C7 6E 9A 28 27 10 F8 2A 7F 37 49 B6 48 35 52 E9 CF 2A B5 F3 26 90 33 68 9B 5A 04 2A 8B F5 78 13 82 FE 3C 13 C4 F9 38 39 0E 02 4C 3D 91 0A 2A 94 3F 9F A6 52 B9 14 89 C5 D9 57 0F 96 F8 0E 7D 32 81 8E 10 DB C0 CA BE C7 3F EC D0 B1 F0 9D A2 4B 9F B3 8D E0 EB 1F 42 52 EA 5E 9E 76 E2 F4 13 9D 0E 7E 6D 0A E3 56 C3 EE 8A 80 24 DE FB 08 82 FB B7 AF CE 2A 69 16 E3 C3 79 5C C7 CD 44 BA AA 08 A2 51 0B 43 31 69 A1 12 D1 AE 48 15 AE 76 E9 AB BB D2 E0 16 03 EB 2D 47 A4 61 24 65 5E CC C5 03 B3 96 3E 7A 39 90 3D DB 63 56 2B 23 85 CE 5F 9E 04 20 45 31 79 7B BF 78 33 77 34 C1 8E 83 B3 50 88 2A 01 C0 C4 E4 BF 2D 0D B9 37 32 AB E0 BB 82 36 B1 4E 51 4B F7 07 6A 12 3E 79 EA 93 3D BD 06 4E AE 1C 49 82 17 14 00 09 59 40 A6 A9 01 56 1A 23 86 A8 33 B3 9A 70 7B 3A C1 F9 31 03 FD DB 4B 5C 7B F9 BB 43 94 65 A0 1C DA 2B 85 AA AD 7B 79 42 F2 EB 25 5E F0 DA B7 E7 AD 4B 25 02 36 BB 78 5F 83 7F F7 78 F0 99 D2 B5 A3 0C 4A 7F 0E B0 A6 C4 99 F7 9E 0B C6 4D FC F5 8D 6B 5F 35 27 36 D3 DB D0 46 C7 10 76 7D 96 91 48 EA 1C B2 B7 D7 2F D2 88 A8 4C 87 D6 A9 40 33 4C 76 C5 48 3E 32 4D C1 C3 7F 5C D9 B3 22 00 88 BE 04 82 64 A9 73 AA E1 65 1A EF 49 B4 54 74 53 FF 75 B6 E9 57 1B 89 2D 6F 2A 6A CE 23 BF 41 CB 55 B3 A0 53 87 AD A0 22 EE 6B 3F 4A 97 23 36 BF 7E 08 2D 0A 9E 2E 4B F2 2E 00 59 EC F1 21 34 45 75 DB 6B F2 EC 65 24 30 69 50 CC 45 78 00 AF C8 F6 3D 8E 03 60 CF CA A1 88 14 18 82 6F 56 58 D0 BC E0 48 FD AA 86 63 CA C1 01 63 07 16 4A 79 79 17 9D 1F E2 40 4B B6 77 6E 44 84 DE BE 02 4C 33 7A F5 2F 93 21 3E 17 62 38 81 95 E6 84 8B 7C C8 7B E2 23 FB 12 4F E8 42 5F 1D 48 92 84 B1 45 FF 69 97 3C 30 C9 09 E8 84 E8 07 0E 17 D4 A1 CC 35 D6 FE 7B D2 9A 44 8B 17 BF E7 D6 98 1D 98 D7 30 BE 55 19 A9 F4 D6 0D E8 18 80 35 85 B6 AB B9 20 32 C7 ED C6 AD A7 AE 19 48 B7 17 02 B3 45 C3 A2 B9 C9 B7 58 B5 8B 4C AF 52 AD A1 E1 62 45 AB 58 26 67 20 C7 64 AA DA 7E F3 70 8B C2 92 69 E3 3E 3E 6F 39 6F 2B 35 35 0F 00 FC 52 B5 5C 5B 73 FE F6 F5 10 55 36 7C 9A 84 FC A6 23 29 4A 75 49 7C 13 1C CA 54 A2 A2 FA 2A 63 A5 4C 9A B4 27 E8 5F 9F 23 96 B2 E7 AA E6 8B E0 E2 6A 75 8A B2 F4 E4 7E 09 E8 22 70 2A 42 8B E3 DC AD E8 A8 A2 92 71 6B A2 12 78 E1 DA CC 70 57 67 F5 B4 52 F3 B4 4C 17 AB 05 33 DA 6E 47 52 C5 B2 B7 9A D2 A8 BC 44 64 D3 26 1A 6B C6 C5 36 1C 2B 8F BD B7 27 91 3E C0 C2 FC 03 41 FE 02 D3 4B B1 E5 5F 5B 50 05 29 BD 3A 64 85 E3 8C FB 11 F2 1D 94 DB D7 78 AF AD 77 A3 9C D4 39 5D 8B EA DF 9D 08 CA 92 7C 5F D5 17 49 0E FA A1 21 1C 9F C3 88 1A DC E7 D8 82 80 85 86 32 99 15 E4 89 BA 91 2B 4B FB 87 EC 44 B4 D9 83 CC 79 77 A4 A0 D0 50 E3 4F 00 E7 DA DA 79 38 1E D8 04 86 16 CD 25 BE BA 76 E4 8C F9 86 91 69 6E C7 A0 EF 6B 44 2B C9 C3 DC 8D 2D 65 60 7A F4 37 02 D4 8F 38 D0 D5 20 30 DE A5 F5 A8 75 C7 EE 0B 0F 1B 88 C2 8A CC 6F 70 1D E4 D8 4E DD 04 A5 5B B8 04 B1 29 42 08 92 19 78 E2 26 EB 6B 07 49 DE 8A AF A3 41 72 1D E2 3C 62 0F 7E 7B DE A3 0F 71 8C 5D EC E9 96 96 45 A9 39 33 8A 87 C9 93 CE 3B 6D 75 50 21 1F 4C 03 E9 A7 AD 03 0F 5E A9 EE 60 CC EA 05 4F DF E1 B1 13 A6 7D C7 B9 37 58 53 3B 06 1A AD 98 E5 06 D9 74 2A B1 96 75 DE A6 B7 89 25 53 2A A3 07 B6 70 C6 86 1F 59 EB 53 08 57 6E 86 D7 A1 5C DB 26 D7 86 3E 97 BB FD 6A 0A 4C E1 81 B9 4C C1 A0 49 89 57 29 E0 CD 79 6F 0A 46 C1 C6 62 75 49 C6 9A B9 22 75 EE 10 C7 56 E6 D5 DE 4D EC 89 5A 6F AC 60 0F B3 CC 37 9E F2 BE 49 A7 77 3C 05 AE 92 66 C8 BE 16 E5 35 17 24 18 A5 CE B8 BB AE CD 88 DE 01 53 40 84 E0 06 C6 77 96 09 DF D7 76 3B CA C9 B5 B2 91 95 07 54 6F 51 EB 12 58 16 8A AF C3 E3 B9 4A EC 25 A5 D1 19 59 72 F5 E3 4F 7C 40 B2 D0 4E 9F 50 13 FB 86 C3 6A 88 32 5B 67 EC 4F 0E 0B 31 F8 0C 02 6C CE 8D 50 55 A2 B3 57 73 7C 78 D3 43 1F 48 33 51 E7 0A D0 6D 46 71 4A AD 66 50 F9 96 11 4F A5 5B 3C A0 3E 46 D2 CB 3B A1 03 84 9C 8E 4E 2D 83 69 2E 17 9B F8 36 63 F1 93 CA F9 32 57 2B AB 4E 14 A3 5A F1 39 B0 3F 0F 99 CC 9B FB 7E BC 0A AA C9 65 3C C8 B4 B0 1F // 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 40 3C 63 DC A2 8F FC E7 09 66 62 11 A3 5A B6 AB DC 6E A1 CA CF E2 0A 6F A8 6D 36 64 4E 22 4B A9 8A ED 07 7A 0A 9E F3 C7 7B 72 EF C1 C7 6E 9A 28 27 10 F8 2A 7F 37 49 B6 48 35 52 E9 CF 2A B5 F3 26 90 33 68 9B 5A 04 2A 8B F5 78 13 82 FE 3C 13 C4 F9 38 39 0E 02 4C 3D 91 0A 2A 94 3F 9F A6 52 B9 14 89 C5 D9 57 0F 96 F8 0E 7D 32 81 8E 10 DB C0 CA BE C7 3F EC D0 B1 F0 9D A2 4B 9F B3 8D E0 EB 1F 42 52 EA 5E 9E 76 E2 F4 13 9D 0E 7E 6D 0A E3 56 C3 EE 8A 80 24 DE FB 08 82 FB B7 AF CE 2A 69 16 E3 C3 79 5C C7 CD 44 BA AA 08 A2 51 0B 43 31 69 A1 12 D1 AE 48 15 AE 76 E9 AB BB D2 E0 16 03 EB 2D 47 A4 61 24 65 5E CC C5 03 B3 96 3E 7A 39 90 3D DB 63 56 2B 23 85 CE 5F 9E 04 20 45 31 79 7B BF 78 33 77 34 C1 8E 83 B3 50 88 2A 01 C0 C4 E4 BF 2D 0D B9 37 32 AB E0 BB 82 36 B1 4E 51 4B F7 07 6A 12 3E 79 EA 93 3D BD 06 4E AE 1C 49 82 17 14 00 09 59 40 A6 A9 01 56 1A 23 86 A8 33 B3 9A 70 7B 3A C1 F9 31 03 FD DB 4B 5C 7B F9 BB 43 94 65 A0 1C DA 2B 85 AA AD 7B 79 42 F2 EB 25 5E F0 DA B7 E7 AD 4B 25 02 36 BB 78 5F 83 7F F7 78 F0 99 D2 B5 A3 0C 4A 7F 0E B0 A6 C4 99 F7 9E 0B C6 4D FC F5 8D 6B 5F 35 27 36 D3 DB D0 46 C7 10 76 7D 96 91 48 EA 1C B2 B7 D7 2F D2 88 A8 4C 87 D6 A9 40 33 4C 76 C5 48 3E 32 4D C1 C3 7F 5C D9 B3 22 00 88 BE 04 82 64 A9 73 AA E1 65 1A EF 49 B4 54 74 53 FF 75 B6 E9 57 1B 89 2D 6F 2A 6A CE 23 BF 41 CB 55 B3 A0 53 87 AD A0 22 EE 6B 3F 4A 97 23 36 BF 7E 08 2D 0A 9E 2E 4B F2 2E 00 59 EC F1 21 34 45 75 DB 6B F2 EC 65 24 30 69 50 CC 45 78 00 AF C8 F6 3D 8E 03 60 CF CA A1 88 14 18 82 6F 56 58 D0 BC E0 48 FD AA 86 63 CA C1 01 63 07 16 4A 79 79 17 9D 1F E2 40 4B B6 77 6E 44 84 DE BE 02 4C 33 7A F5 2F 93 21 3E 17 62 38 81 95 E6 84 8B 7C C8 7B E2 23 FB 12 4F E8 42 5F 1D 48 92 84 B1 45 FF 69 97 3C 30 C9 09 E8 84 E8 07 0E 17 D4 A1 CC 35 D6 FE 7B D2 9A 44 8B 17 BF E7 D6 98 1D 98 D7 30 BE 55 19 A9 F4 D6 0D E8 18 80 35 85 B6 AB B9 20 32 C7 ED C6 AD A7 AE 19 48 B7 17 02 B3 45 C3 A2 B9 C9 B7 58 B5 8B 4C AF 52 AD A1 E1 62 45 AB 58 26 67 20 C7 64 AA DA 7E F3 70 8B C2 92 69 E3 3E 3E 6F 39 6F 2B 35 35 0F 00 FC 52 B5 5C 5B 73 FE F6 F5 10 55 36 7C 9A 84 FC A6 23 29 4A 75 49 7C 13 1C CA 54 A2 A2 FA 2A 63 A5 4C 9A B4 27 E8 5F 9F 23 96 B2 E7 AA E6 8B E0 E2 6A 75 8A B2 F4 E4 7E 09 E8 22 70 2A 42 8B E3 DC AD E8 A8 A2 92 71 6B A2 12 78 E1 DA CC 70 57 67 F5 B4 52 F3 B4 4C 17 AB 05 33 DA 6E 47 52 C5 B2 B7 9A D2 A8 BC 44 64 D3 26 1A 6B C6 C5 36 1C 2B 8F BD B7 27 91 3E C0 C2 FC 03 41 FE 02 D3 4B B1 E5 5F 5B 50 05 29 BD 3A 64 85 E3 8C FB 11 F2 1D 94 DB D7 78 AF AD 77 A3 9C D4 39 5D 8B EA DF 9D 08 CA 92 7C 5F D5 17 49 0E FA A1 21 1C 9F C3 88 1A DC E7 D8 82 80 85 86 32 99 15 E4 89 BA 91 2B 4B FB 87 EC 44 B4 D9 83 CC 79 77 A4 A0 D0 50 E3 4F 00 E7 DA DA 79 38 1E D8 04 86 16 CD 25 BE BA 76 E4 8C F9 86 91 69 6E C7 A0 EF 6B 44 2B C9 C3 DC 8D 2D 65 60 7A F4 37 02 D4 8F 38 D0 D5 20 30 DE A5 F5 A8 75 C7 EE 0B 0F 1B 88 C2 8A CC 6F 70 1D E4 D8 4E DD 04 A5 5B B8 04 B1 29 42 08 92 19 78 E2 26 EB 6B 07 49 DE 8A AF A3 41 72 1D E2 3C 62 0F 7E 7B DE A3 0F 71 8C 5D EC E9 96 96 45 A9 39 33 8A 87 C9 93 CE 3B 6D 75 50 21 1F 4C 03 E9 A7 AD 03 0F 5E A9 EE 60 CC EA 05 4F DF E1 B1 13 A6 7D C7 B9 37 58 53 3B 06 1A AD 98 E5 06 D9 74 2A B1 96 75 DE A6 B7 89 25 53 2A A3 07 B6 70 C6 86 1F 59 EB 53 08 57 6E 86 D7 A1 5C DB 26 D7 86 3E 97 BB FD 6A 0A 4C E1 81 B9 4C C1 A0 49 89 57 29 E0 CD 79 6F 0A 46 C1 C6 62 75 49 C6 9A B9 22 75 EE 10 C7 56 E6 D5 DE 4D EC 89 5A 6F AC 60 0F B3 CC 37 9E F2 BE 49 A7 77 3C 05 AE 92 66 C8 BE 16 E5 35 17 24 18 A5 CE B8 BB AE CD 88 DE 01 53 40 84 E0 06 C6 77 96 09 DF D7 76 3B CA C9 B5 B2 91 95 07 54 6F 51 EB 12 58 16 8A AF C3 E3 B9 4A EC 25 A5 D1 19 59 72 F5 E3 4F 7C 40 B2 D0 4E 9F 50 13 FB 86 C3 6A 88 32 5B 67 EC 4F 0E 0B 31 F8 0C 02 6C CE 8D 50 55 A2 B3 57 73 7C 78 D3 43 1F 48 33 51 E7 0A D0 6D 46 71 4A AD 66 50 F9 96 11 4F A5 5B 3C A0 3E 46 D2 CB 3B A1 03 84 9C 8E 4E 2D 83 69 2E 17 9B F8 36 63 F1 93 CA F9 32 57 2B AB 4E 14 A3 5A F1 39 B0 3F 0F 99 CC 9B FB 7E BC 0A AA C9 65 3C C8 B4 B0 1F
val flag2 = readByte().toInt() val flag2 = readByte().toInt()
PacketLogger.verbose("包类型(flag2) = $flag2. (可能是 ${if (flag2 == 2) "OicqRequest" else "Uni"})") PacketLogger.verbose { "包类型(flag2) = $flag2. (可能是 ${if (flag2 == 2) "OicqRequest" else "Uni"})" }
val flag3 = readByte().toInt() val flag3 = readByte().toInt()
check(flag3 == 0) { "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. Remaining=${this.readBytes().toUHexString()}" } check(flag3 == 0) { "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. Remaining=${this.readBytes().toUHexString()}" }
@ -185,15 +185,15 @@ internal object KnownPacketFactories {
kotlin.runCatching { kotlin.runCatching {
// 快速解密 // 快速解密
if (flag2 == 2) { if (flag2 == 2) {
PacketLogger.verbose("SSO, 尝试使用 16 zero 解密.") PacketLogger.verbose { "SSO, 尝试使用 16 zero 解密." }
data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose("成功使用 16 zero 解密") } data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } }
} else { } else {
PacketLogger.verbose("Uni, 尝试使用 d2Key 解密.") PacketLogger.verbose { "Uni, 尝试使用 d2Key 解密." }
data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose("成功使用 d2Key 解密") } data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } }
} }
}.getOrElse { }.getOrElse {
// 慢速解密 // 慢速解密
PacketLogger.verbose("失败, 尝试其他各种key") PacketLogger.verbose { "失败, 尝试其他各种key" }
bot.client.tryDecryptOrNull(data, size) { it } bot.client.tryDecryptOrNull(data, size) { it }
}?.toReadPacket()?.let { decryptedData -> }?.toReadPacket()?.let { decryptedData ->
// 解析外层包装 // 解析外层包装
@ -205,8 +205,8 @@ internal object KnownPacketFactories {
}?.let { }?.let {
// 处理内层真实的包 // 处理内层真实的包
if (it.packetFactory == null) { if (it.packetFactory == null) {
PacketLogger.warning("找不到 PacketFactory") PacketLogger.warning { "找不到 PacketFactory" }
PacketLogger.verbose("传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> data.toUHexString(length = length) }}") PacketLogger.verbose { "传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> data.toUHexString(length = length) }}" }
return return
} }
@ -235,7 +235,7 @@ internal object KnownPacketFactories {
} }
} ?: inline { } ?: inline {
// 无法解析 // 无法解析
PacketLogger.error("任何key都无法解密: ${data.take(size).toUHexString()}") PacketLogger.error{"任何key都无法解密: ${data.take(size).toUHexString()}"}
return return
} }
} }
@ -260,20 +260,17 @@ internal object KnownPacketFactories {
// head // head
input.readPacket(input.readInt() - 4).withUse { input.readPacket(input.readInt() - 4).withUse {
ssoSequenceId = readInt() ssoSequenceId = readInt()
PacketLogger.verbose("sequenceId = $ssoSequenceId") PacketLogger.verbose { "sequenceId = $ssoSequenceId" }
val returnCode = readInt() val returnCode = readInt()
if (returnCode != 0) { if (returnCode != 0) {
error("returnCode = $returnCode") error("returnCode = $returnCode")
} }
val extraData = readBytes(readInt() - 4) val extraData = readBytes(readInt() - 4)
PacketLogger.verbose("(sso/inner)extraData = ${extraData.toUHexString()}") PacketLogger.verbose { "(sso/inner)extraData = ${extraData.toUHexString()}" }
commandName = readString(readInt() - 4) commandName = readString(readInt() - 4)
bot.client.outgoingPacketUnknownValue = readBytes(readInt() - 4) bot.client.outgoingPacketSessionId = readBytes(readInt() - 4)
if (commandName == "ConfigPushSvc.PushReq") {
bot.client.configPushSvcPushReqSequenceId = ssoSequenceId
}
dataCompressed = readInt() dataCompressed = readInt()
} }

View File

@ -21,8 +21,8 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SyncCookie
import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.network.protocol.packet.*
import net.mamoe.mirai.qqandroid.utils.toMessageChain import net.mamoe.mirai.qqandroid.message.toMessageChain
import net.mamoe.mirai.qqandroid.utils.toRichTextElems import net.mamoe.mirai.qqandroid.message.toRichTextElems
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
import kotlin.math.absoluteValue import kotlin.math.absoluteValue

View File

@ -27,9 +27,7 @@ internal class ConfigPushSvc {
return network.run { return network.run {
buildResponseUniPacket( buildResponseUniPacket(
client, client,
sequenceId = client.configPushSvcPushReqSequenceId, sequenceId = sequenceId
commandName = "ConfigPushSvc.PushResp",
name = "ConfigPushSvc.PushResp"
) { ) {
writeJceStruct( writeJceStruct(
RequestPacket.serializer(), RequestPacket.serializer(),

View File

@ -126,14 +126,14 @@ fun Map<Int, ByteArray>.printTLVMap(name: String = "", keyLength: Int = 2) =
fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed", { buildPacket { writeInt(it.size + 4); writeFully(it) } }) { fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed", { buildPacket { writeInt(it.size + 4); writeFully(it) } }) {
val flag1 = readInt() val flag1 = readInt()
println("flag1=" + flag1.contentToString()) print("flag1=" + flag1.contentToString() + ", ")
val flag2 = readByte().toInt() val flag2 = readByte().toInt()
println("flag2=$flag2") print("flag2=$flag2" + ", ")
if (flag1 == 0x0B) { if (flag1 == 0x0B) {
if (flag2 == 1) { if (flag2 == 1) {
println("sequenceId = " + readInt().toUHexString()) print("sequenceId = " + readInt().toUHexString() + ", ")
} else { } else {
println("extra data=" + readBytes(readInt() - 4).toUHexString()) print("extra data=" + readBytes(readInt() - 4).toUHexString() + ", ")
} }
} else { } else {
//if (flag2 == 1) { //if (flag2 == 1) {
@ -145,15 +145,15 @@ fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed
// } // }
} }
println("flag3=" + readByte().toUHexString()) print("flag3=" + readByte().toUHexString() + ", ")
println("uin=" + readString(readInt() - 4)) readString(readInt() - 4)
println("// 解密 body") print("// 解密 body")
val encrypted = readBytes() val encrypted = readBytes()
val decrypted = encrypted.tryDecryptOrNull() val decrypted = encrypted.tryDecryptOrNull()
if (decrypted == null) { if (decrypted == null) {
println("cannot decrypt: ${encrypted.toUHexString()}") println(", cannot decrypt: ${encrypted.toUHexString()}")
error("cannot decrypt: ${encrypted.toUHexString()}") error("cannot decrypt: ${encrypted.toUHexString()}")
} else { } else {
decrypted.toReadPacket().debugPrintThis("outer body decrypted").apply { decrypted.toReadPacket().debugPrintThis("outer body decrypted").apply {

View File

@ -16,6 +16,10 @@ object QLogReader {
return (decompress(file.readBytes())) return (decompress(file.readBytes()))
} }
fun readQLog(file: ByteArray): String {
return (decompress(file))
}
fun decompress(array: ByteArray): String { fun decompress(array: ByteArray): String {
return buildString { return buildString {

View File

@ -305,12 +305,12 @@ class MessageSubscribersBuilder<T : MessagePacket<*, *>>(
): Listener<T> { ): Listener<T> {
return if (trim) { return if (trim) {
val toCheck = suffix.trim() val toCheck = suffix.trim()
content({ it.trimStart().startsWith(toCheck) }, { content({ it.trimEnd().endsWith(toCheck) }, {
if (removeSuffix) this.onEvent(this.message.toString().substringBeforeLast(toCheck).trim()) if (removeSuffix) this.onEvent(this.message.toString().removeSuffix(toCheck).trim())
else onEvent(this, this.message.toString().trim()) else onEvent(this, this.message.toString().trim())
}) })
} else { } else {
content({ it.startsWith(suffix) }, { content({ it.endsWith(suffix) }, {
if (removeSuffix) this.onEvent(this.message.toString().removeSuffix(suffix)) if (removeSuffix) this.onEvent(this.message.toString().removeSuffix(suffix))
else onEvent(this, this.message.toString()) else onEvent(this, this.message.toString())
}) })

View File

@ -0,0 +1,8 @@
package net.mamoe.mirai.message.data
/**
* 消息源, 用于被引用. 它将由协议模块实现为 `MessageSourceImpl`
*/
interface MessageSource : Message {
companion object : Message.Key<MessageSource>
}

View File

@ -0,0 +1,11 @@
package net.mamoe.mirai.message.data
/**
* 群内的引用回复. 它将由协议模块实现为 `QuoteReplyImpl`
*/
interface QuoteReply : Message {
val source: MessageSource
companion object Key : Message.Key<QuoteReply>
}