Merge pull request #1 from mamoe/master

re
This commit is contained in:
Cyenoch 2020-03-11 22:43:20 +08:00 committed by GitHub
commit b8f49b5037
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
314 changed files with 18074 additions and 19446 deletions

2
.editorconfig Normal file
View File

@ -0,0 +1,2 @@
[*.{kt, kts}]
max_line_length = 120

10
.github/ISSUE_TEMPLATE/-------.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: 疑问 / 帮助
about: 询问一个问题
title: ''
labels: question
assignees: ''
---
<!--请详细描述你的问题. 我们会尊重每一个问题, 你不必担心问题是否过于简单-->

21
.github/ISSUE_TEMPLATE/----.md vendored Normal file
View File

@ -0,0 +1,21 @@
---
name: 特性申请
about: 申请 mirai 添加新的特性
title: ''
labels: feature
assignees: ''
---
<!--
以下相关功能将会被直接拒绝:
- 资金相关: 红包, 转账
- 主动加好友, 主动加群, 主动邀请加入群
可以提交的内容:
- 有较高使用频率的协议 (低频功能不接受提议)
- 架构 / 功能上的建议 (非常欢迎,我们会尊重你的建议)
-->
<!--请在下一行开始描述你的问题-->

22
.github/ISSUE_TEMPLATE/bug---.md vendored Normal file
View File

@ -0,0 +1,22 @@
---
name: Bug 报告
about: 提交一个 bug
title: ''
labels: " bug "
assignees: ''
---
## 问题
<!--在这里简略描述你遇到的问题-->
<!--如果有控制台报错,请尽量附加全面的日志或截图-->
## 如何复现
<!--在这里简略说明如何让这个问题再次发生-->
<!--可使用 1. 2. 3. 的列表格式,或其他任意恰当的格式>
<!--如有必要,你可以在下文继续添加其他信息-->

View File

@ -1,6 +1,6 @@
name: CI name: Gradle CI
on: [push] on: [push, pull_request]
jobs: jobs:
build: build:
@ -8,10 +8,12 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v2
- name: setup-android - name: Set up JDK 1.8
uses: msfjarvis/setup-android@0.2 uses: actions/setup-java@v1
with: with:
# Gradle tasks to run - If you want to run ./gradlew assemble, specify assemble here. java-version: 1.8
gradleTasks: build -x mirai-core:jvmTest - name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build

40
.github/workflows/main2.yml vendored Normal file
View File

@ -0,0 +1,40 @@
# 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"

View File

@ -2,6 +2,200 @@
开发版本. 频繁更新, 不保证高稳定性 开发版本. 频繁更新, 不保证高稳定性
## `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 月)协议
- 全面的 `Image` 类型: Online/Offline Image, Friend/Group Image
- 修复查询图片链接时好友图片链接错误的问题
- 修复 bugs: #105, #106, #107
## `0.24.1` 2020/3/3
- 修复 `Member` 的委托 `QQ` 弱引用被释放的问题
- 用 `Bot.friends` 替代 `Bot.qqs`
- 用 `Bot.containsFriend`, `Bot.containsGroup` 替代 `Bot.contains`
- 新增 `BotFactory.Bot(String, ByteArray)` 用 md5 密码登录
- 为 `BotFactory` 等类型的一些扩展指定 `JvmName`
- 移动 `Bot.QQ` 到低级 API
## `0.24.0` 2020/3/1
- Java 完全友好: Java 使用者可以同 Kotlin 方式直接阻塞式或异步Future调用 API
- 新增 `MessageSource.originalMessage: MessageChain` 以获取源消息内容
- 群消息的撤回现在已稳定 (`Bot.recall`)
- 现在可以引用回复机器人自己发送的消息: `MessageReceipt.quoteReply`
- 新增 `MessageRecallEvent`
- 整理 `MessageChain` 的构造, 优化性能
- 整理所有网络层代码, 弃用 `kotlinx.io` 而使用 `io.ktor.utils.io`
- 其他杂项优化
## `0.23.0` 2020/2/28
### mirai-core
- 修复上传图片
- 一些问题修复
- 大量杂项优化
### mirai-core-qqandroid
- `MessageReceipt.source` 现在为 public. 可获取源消息 id
- 修复上传好友图片失败的问题
- 上传群图片现在分包缓存, 优化性能
## `0.22.0` 2020/2/24
### mirai-core
- 重构 `MessageChain`, 引入 `CombinedMessage`. (兼容大部分原 API)
- 新增 `MessageChainBuilder`, `buildMessageChain`
- `ExternalImage` 现在接收多种输入参数
### mirai-core-qqandroid
- 修复访问好友消息回执 `.sequenceId` 时抛出异常的问题
## `0.21.0` 2020/2/23
- 支持好友消息的引用回复
- 更加结构化的 `QuoteReply` 架构, 支持引用任意群/好友消息回复给任意群/好友.
## `0.20.0` 2020/2/23
### mirai-core
- 支持图片下载: `image.channel(): ByteReadChannel`, `image.url()`
- 添加 `LockFreeLinkedList<E>.iterator`
- 添加 `LockFreeLinkedList<E>.forEachNode`
- 并行处理事件监听
- 添加 `nextMessageContaining` 和相关可空版本
- '撤回' 从 `Contact` 移动到 `Bot`
- 删除 `MessageSource.sourceMessage`
- 让 MessageSource 拥有唯一的 long 类型 id, 删除原 `uid``sequence` 结构.
- 修复 `Message.eq` 歧义
## `0.19.1` 2020/2/21
### mirai-core
- 支持机器人撤回群消息 (含自己发送的消息): `Group.recall`, `MessageReceipt.recall`
- 支持一定时间后自动撤回: `Group.recallIn`, `MessageReceipt.recallIn`
- `sendMessage` 返回 `MessageReceipt` 以实现撤回功能
- 添加 `MessageChain.addOrRemove`
- 添加 `ContactList.firstOrNull`, `ContactList.first`
- 新的异步事件监听方式: `subscribingGetAsync` 启动一个协程并从一个事件从获取返回值到 `Deferred`.
- 新的线性事件监听方式: `subscribingGet` 挂起当前协程并从一个事件从获取返回值.
##### 新的线性消息连续处理: `nextMessage` 挂起当前协程并等待下一条消息:
使用该示例, 发送两条消息, 一条为 "禁言", 另一条包含一个 At
```kotlin
case("禁言") {
val value: At = nextMessage { message.any(At) }[At]
value.member().mute(10)
}
```
示例 2:
```kotlin
case("复读下一条") {
reply(nextMessage().message)
}
```
### mirai-core-qqandroid
- 修复一些情况下 `At` 无法发送的问题
- 统一 ImageId: 群消息收到的 ImageId 均为 `{xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxx}.jpg` 形式(固定长度 37
- 支持成员主动离开事件的解析 (#51)
## `0.18.0` 2020/2/20
### mirai-core
- 添加 `MessageSource.time`
- 添加事件监听时额外的 `coroutineContext`
- 为一些带有 `operator` 的事件添加 `.isByBot` 的属性扩展
- 优化事件广播逻辑, 修复可能无法触发监听的问题
- 为所有 `Contact` 添加 `toString()` (#80)
### mirai-core-qqandroid
- 支持成员禁言状态和时间查询 `Member.muteTimeRemaining`
- 修复 `At``display` (#73), 同时修复 `QuoteReply` 无法显示问题 (#54).
- 广播 `BotReloginEvent` (#78)
- 支持机器人自身禁言时间的更新和查询 (#82)
## `0.17.0` 2020/2/20
### mirai-core
- 支持原生表情 `Face`
- 修正 `groupCardOrNick``nameCardOrNick`
- 增加 `MessageChain.foreachContent(lambda)``Message.hasContent(): Boolean`
### mirai-core-qqandroid
- 提高重连速度
- 修复重连后某些情况不会心跳
- 修复收包时可能产生异常
## `0.16.0` 2020/2/19
### mirai-core
- 添加 `Bot.subscribe` 等筛选 Bot 实例的监听方法
- 其他一些小问题修复
### mirai-core-qqandroid
- 优化重连处理逻辑
- 确保好友消息和历史事件在初始化结束前同步完成
- 同步好友消息记录时不广播
## `0.15.5` 2020/2/19
### mirai-core
- 为 `MiraiLogger` 添加 common property `val isEnabled: Boolean`
- 修复 #62: 掉线重连后无 heartbeat
- 修复 #65: `Bot` close 后仍会重连
- 修复 #70: ECDH is not available on Android platform
### mirai-core-qqandroid
- 从服务器收到的事件将会额外使用 `bot.logger` 记录 (verbose).
- 降低包记录的等级: `info` -> `verbose`
- 改善 `Bot` 的 log 记录
- 加载好友列表失败时会重试
- 改善 `Bot``NetworkHandler` 关闭时取消 job 的逻辑
- 修复初始化(init)时同步历史好友消息时出错的问题
## `0.15.4` 2020/2/18
- 放弃使用 `atomicfu` 以解决其编译错误的问题. (#60)
## `0.15.3` 2020/2/18
- 修复无法引入依赖的问题.
## `0.15.2` 2020/2/18
### mirai-core
- 尝试修复 `atomicfu` 编译错误的问题
### mirai-core-qqandroid
- 查询群信息失败后重试
## `0.15.1` 2020/2/15
### mirai-core
- 统一异常处理: 所有群成员相关操作无权限时均抛出异常而不返回 `false`.
### mirai-core-qqandroid
- 初始化未完成时缓存接收的所有事件包 (#46)
- 解析群踢人事件时忽略找不到的群成员
- 登录完成后广播事件 `BotOnlineEvent`
## `0.15.0` 2020/2/14 ## `0.15.0` 2020/2/14
### mirai-core ### mirai-core
@ -132,7 +326,7 @@ TIMPC
## `0.10.0` *2019/12/23* ## `0.10.0` *2019/12/23*
**事件优化** **事件优化**
更快的监听过程 更快的监听过程
现在监听不再是 `suspend`, 而必须显式指定 `CoroutineScope`. 详见 [`Subscribers.kt`](mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt#L69) 现在监听不再是 `suspend`, 而必须显式指定 `CoroutineScope`. 详见 `Subscribers.kt`
删除原本的 bot.subscribe 等监听模式. 删除原本的 bot.subscribe 等监听模式.
**其他** **其他**

29
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,29 @@
# 贡献
感谢你来到这里和你对 mirai 做的所有贡献。
mirai 欢迎一切形式的代码贡献。你可以通过以下几种途径向 mirai 贡献。
## 主仓库 `mirai-core`
### 代码优化
优化功能设计或实现, 或是引入一个新的设计(建议先通过 issue 与我们达成共识)
### 协议更新
为 mirai 添加更广泛的协议支持。
### 注意事项
- mirai 框架已经把实现协议需要做的工作最小化. 为避免工作重复, 请务必熟悉 `net.mamoe.mirai.utils``net.mamoe.mirai.qqandroid.utils` 中工具类
- mirai 使用 [`kotlinx.io`](https://github.com/Kotlin/kotlinx-io) IO 库
- mirai 为多平台项目, 请务必考虑多平台兼容性
- mirai 为全协程实现, 请在有必要的时候考虑并发安全性
- 尽量不要引用新的库
- 遵守 Kotlin 代码规范(提交前使用 IDE 格式化代码)
- 熟悉 [`PacketFactory`](https://github.com/mamoe/mirai/blob/master/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/PacketFactory.kt) 架构
- 不要手动拆解数据包. 请一定使用 `kotlinx.serialization` 拆解 ProtoBuf 和使用 mirai 的 `Jce` 序列化器拆解 Jce 数据包
- 必须保证高代码效率(使用 `ByteArrayPool``WeakRef` 等)
## 社区
插件社区不要求太高的代码质量,任何人都可以帮助 mirai。
可以为 [mirai-console](https://github.com/mamoe/mirai-console) 编写插件, 并发布到社区网站: (建设中)

View File

@ -6,14 +6,16 @@ Some of the protocol came from the other open-source projects.
**The development is only for learning, DO NOT use it for illegal purposes.** **The development is only for learning, DO NOT use it for illegal purposes.**
## UpdateLog ## Changelog
You can inspect supported protocols at [Project](https://github.com/mamoe/mirai/projects/1) You can inspect supported protocols at [Project](https://github.com/mamoe/mirai/projects/1)
and logs of updates at [UpdateLog](https://github.com/mamoe/mirai/blob/master/UpdateLog.md) and logs of updates at [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md)
## Use as a library ## Use as a library
You can install mirai as a library into your project. You can install mirai as a library into your project.
Mirai is only published on `jcenter`, therefore please ensure you have the `jcenter()` repository in your `build.gradle`, like: Mirai is only published on `jcenter`, therefore please ensure you have the `jcenter()` repository in your `build.gradle`.
```kotlin ```kotlin
repositories{ repositories{
jcenter() jcenter()
@ -24,6 +26,7 @@ If your project is a multiplatform project, you should add dependencies for each
If your project is not a multiplatform project, you just need to add the platform-specific dependency. If your project is not a multiplatform project, you just need to add the platform-specific dependency.
`VERSION` should be replaced with the newest version, say [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) `VERSION` should be replaced with the newest version, say [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
Mirai is still under experimental stage, it is suggested to keep the version newest. Mirai is still under experimental stage, it is suggested to keep the version newest.
**common** **common**
@ -71,14 +74,20 @@ If you meet any problem or have any questions, be free to open a issue. Our goal
Kotlin 1.3.61 Kotlin 1.3.61
On JVM: Java 6 On JVM: Java 6
On Android: SDK 15 On Android: SDK 15
### Using java ### Using java
Q: Can I use Mirai without Kotlin? Q: Can I use Mirai without Kotlin?
A: Calling from java is not yet supported. Coroutines, extensions and inlines, which are difficult to use from Java, are generally used in Mirai. Therefore you should have the skill of Kotlin before you use Mirai. A: Calling from java is not yet supported. Coroutines, extensions and inlines, which are difficult to use from Java, are generally used in Mirai. Therefore you should have the skill of Kotlin before you use Mirai.
#### Libraries used ## Acknowledgements
Mirai uses these open-source libraries.
Thanks to [JetBrains](https://www.jetbrains.com/?from=mirai) for allocating free open-source licences for IDEs such as [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai).
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai)
### Third Party Libraries
- [kotlin-stdlib](https://github.com/JetBrains/kotlin) - [kotlin-stdlib](https://github.com/JetBrains/kotlin)
- [kotlinx-coroutines](https://github.com/Kotlin/kotlinx.coroutines) - [kotlinx-coroutines](https://github.com/Kotlin/kotlinx.coroutines)
@ -92,7 +101,19 @@ Mirai uses these open-source libraries.
- [javafx](https://github.com/openjdk/jfx) - [javafx](https://github.com/openjdk/jfx)
- [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization) - [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization)
## License
## Acknowledgement Copyright (C) 2019-2020 mamoe and Mirai contributors
Thanks to [JetBrains](https://www.jetbrains.com/?from=mirai) for allocating free open-source licences for IDEs such as [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai).
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai) This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

205
README.md
View File

@ -1,151 +1,146 @@
# Mirai <div align="center">
<img width="160" src="http://img.mamoe.net/2020/02/16/a759783b42f72.png" alt="logo"></br>
<img width="95" src="http://img.mamoe.net/2020/02/16/c4aece361224d.png" alt="title">
----
[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[![Actions Status](https://github.com/mamoe/mirai/workflows/CI/badge.svg)](https://github.com/mamoe/mirai/actions) ![Gradle CI](https://github.com/mamoe/mirai/workflows/Gradle%20CI/badge.svg?branch=master)
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持的高效率机器人框架
这个项目的名字来源于
<p><a href = "http://www.kyotoanimation.co.jp/">京都动画</a>作品<a href = "https://zh.moegirl.org/zh-hans/%E5%A2%83%E7%95%8C%E7%9A%84%E5%BD%BC%E6%96%B9">《境界的彼方》</a><a href = "https://zh.moegirl.org/zh-hans/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5">栗山未来(Kuriyama <b>Mirai</b>)</a></p>
<p><a href = "https://www.crypton.co.jp/">CRYPTON</a><a href = "https://www.crypton.co.jp/miku_eng">初音未来</a>为代表的创作与活动<a href = "https://magicalmirai.com/2019/index_en.html">(Magical <b>Mirai</b>)</a></p>
图标以及形象由画师<a href = "">DazeCake</a>绘制
</div>
## Mirai
**[English](README-eng.md)** **[English](README-eng.md)**
多平台 **QQ Android 和 TimPC** 协议支持库与高效率的机器人框架.
多平台 **QQ Android****TIM PC** 协议支持库与高效率的机器人框架.
纯 Kotlin 实现协议和支持框架,模块<b>全部免费开源</b> 纯 Kotlin 实现协议和支持框架,模块<b>全部免费开源</b>
目前可运行在 JVM 或 Android。 目前可运行在 JVM 或 Android 平台
Mirai既可以作为你项目中的QQ协议支持Lib, 也可以作为单独的Application与插件承载QQ机器人 mirai 既可以作为你项目中的 QQ 协议支持库, 也可以作为单独的应用程序与插件承载 QQ 机器人服务。
**一切开发旨在学习,请勿用于非法用途** **一切开发旨在学习,请勿用于非法用途**
加入 Gitter, 或加入 QQ 群: 655057127 加入 [![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge), 或加入 QQ 群: 655057127
## CHANGELOG
在 [Project](https://github.com/mamoe/mirai/projects/3) 查看已支持功能和计划
在 [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 查看版本更新记录 (仅发布的版本)
## Modules ## 开始
### mirai-core
通用 API 模块,一套 API 适配两套协议。
**请参考此模块的 API**
### mirai-core-qqandroid Mirai 目前为快速流转Moving fast状态, 增量版本之间可能不具有兼容性,任何功能都可能在没有警告的情况下添加、删除或者更改。
QQ for Android 8.2.0 版本2019 年 12 月)协议的实现,目前完成大部分。
- 高兼容性:协议仅含极少部分为硬编码,其余全部随官方方式动态生成
- 高安全性密匙随机ECDH 动态计算
- 已支持大部分使用场景, 详情请在[Project](https://github.com/mamoe/mirai/projects/3)查看
### mirai-core-timpc Mirai 源码完全开放, 您可以参考 Mirai 的协议实现来开发其他框架, 但需注明来源并遵守开源协议要求 (AGPLv3)。
TIM PC 2.3.2 版本2019 年 8 月)协议的实现,相较于 core仅新增少量 API. 详见 [README.md](mirai-core-timpc/)
支持的功能:
- 消息收发:图片文字复合消息,图片消息
- 群管功能:群员列表,禁言
(目前不再更新此协议,请关注上文的安卓协议)
## Use directly ### 开发者
**直接使用 Mirai(终端环境/网页面板(将来)).**
[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动 Mirai 并获得机器人服务
本模块还未完善。
## Use as a library **了解 mirai 架构** [Wiki](https://github.com/mamoe/mirai/wiki/Home)
**mirai-core 为独立设计, 可以作为库内置于任意 Java(JVM)/Android 项目中使用.**
### Gradle #### 使用 mirai 作为服务器,为 mirai 开发插件
Mirai 只发布在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库:
```kotlin
repositories{
jcenter()
}
```
若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。
**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 JVM 运行则只需要`-jvm`的依赖**
请将 `VERSION` 替换为最新的版本(如 `0.13.0`): - (官方)`Java` 或 `Kotlin` 为 [mirai-console](https://github.com/mamoe/mirai-console) 直接编写插件并与其他插件开发者合作共享
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) - (社区)`C`, `C++` 等原生语言: [mirai-native](https://github.com/iTXTech/mirai-native) 支持酷Q插件在mirai上运行
**Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.** - (社区)`Python`: [python-mirai](https://github.com/Chenwe-i-lin/python-mirai) 基于`Mirai-http-api`的 Mirai Framework for Python
- (社区)`JavaScript`(`NodeJS`) [node-mirai](https://github.com/RedBeanN/node-mirai) Mirai的NodeJs SDK
- (官方)其他任意语言: [mirai HTTP 接口](https://github.com/mamoe/mirai-api-http) 进行接入
**注意:** #### 使用 mirai 为第三方依赖库引入项目
Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。
只添加 API 模块将无法正常工作。
现在只推荐使用 TIMPC 协议,请参照下文选择对应目标平台的依赖添加。
**common** Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-common:VERSION")
```
**jvm**
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-jvm:VERSION")
```
**android**
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-android:VERSION")
```
### Performance
Android 上, Mirai 运行需使用 80M 内存.
JVM 上需 120M-150M 内存
## Contribution - `Kotlin` 简略版: [Mirai Guide - Quick Start](/docs/guide_quick_start.md)
- `Kotlin` 新手版: [Mirai Guide - Getting Started](/docs/guide_getting_started.md)
- `Java` 查看上述 Demos
我们 (Mamoe Technologies) 将会一直维护这个项目,除非遇到不可抗力因素。 ### 使用者
- [mirai-console](https://github.com/mamoe/mirai-console) 支持插件 **本模块正在完善**
### 我是其他平台的使用者
#### 酷 Q 平台用户:
- 酷Q的插件可以在 mirai 中加载, 详见 [Mirai-Native](https://github.com/iTXTech/mirai-native)
- 使用 `酷Q HTTP API` 的插件将可以在 mirai 中加载,`Mirai-CQ-Adapter` 正在进行中
## 更新日志
* 在 [Project](https://github.com/mamoe/mirai/projects/3) 查看已支持功能和计划
* 在 [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 查看版本更新记录 (仅发布的版本)
## [贡献](https://github.com/mamoe/mirai/blob/master/CONTRIBUTING.md)
我们欢迎一切形式的贡献。 我们欢迎一切形式的贡献。
我们也期待有更多人能加入 Mirai 的开发。 我们也期待有更多人能加入 mirai 的开发。
若在使用过程中有任何疑问, 可提交 issue 或是邮件联系(support@mamoe.net). 我们希望 Mirai 变得更易用. 若在使用过程中有任何疑问, 可提交 `issue` 或是[邮件联系](mailto:support@mamoe.net). 我们希望 mirai 变得更易用.
您的 star 是对我们最大的鼓励(点击项目右上角); 您的 `star` 是对我们最大的鼓励(点击项目右上角)
## Wiki ### 贡献者
在 [Wiki](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin) 中查看各类帮助,**如 API 示例**(可能过时,待 QQ Android 协议完成后会重写)。 感谢以下全体开发者对 mirai 的贡献(排名不分先后)
## Try [<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/12100985?s=60&v=4" />](https://github.com/Him188)
[<img width="60px" height="60px" src="https://avatars0.githubusercontent.com/u/24618776?s=60&v=4" />](https://github.com/liujiahua123123)
### On JVM or Android [<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/28707253?s=60&v=4" />](https://github.com/ryoii)
现在体验低付出高效率的 Mirai [<img width="60px" height="60px" src="https://avatars1.githubusercontent.com/u/11070535?s=60&v=4" />](https://github.com/jasonczc)
[<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/13656668?s=60&v=4" />](https://github.com/PeratX)
```kotlin [<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/18532671?s=60&v=4" />](https://github.com/uebian)
val bot = TIMPC.Bot(qqId, password).alsoLogin() [<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/10308687?s=60&v=4" />](https://github.com/Freedom0925)
bot.subscribeMessages { [<img width="60px" height="60px" src="https://avatars3.githubusercontent.com/u/16398479?s=60&v=4" />](https://github.com/ice1000)
"你好" reply "你好!" [<img width="60px" height="60px" src="https://avatars0.githubusercontent.com/u/20042607?s=60&v=4" />](https://github.com/PragmaTwice)
"profile" reply { sender.queryProfile() } [<img width="60px" height="60px" src="https://avatars0.githubusercontent.com/u/25280943?s=60&v=4" />](https://github.com/HoshinoTented)
contains("图片"){ File(imagePath).send() } [<img width="60px" height="60px" src="https://avatars3.githubusercontent.com/u/40517459?s=60&v=4" />](https://github.com/Cyenoch)
}
bot.subscribeAlways<MemberPermissionChangedEvent> {
if (it.kind == BECOME_OPERATOR)
reply("${it.member.id} 成为了管理员")
}
```
我们也考虑到了 Java 兼容的问题,这正在计划中,但不是高优先的。
1. Clone
2. Import as Gradle project
3. 运行 Demo 程序: [mirai-demo](#mirai-demo) 示例和演示程序
## Build Requirements ## 鸣谢
- Kotlin 1.3.61 特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 的授权
- JDK 8 (required) [<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai)
- JDK 11for protocol tools, optional
- Android SDK 29 (for Android target, optional) ### 第三方类库(无排名)
#### Libraries used
感谢:
- [kotlin-stdlib](https://github.com/JetBrains/kotlin) - [kotlin-stdlib](https://github.com/JetBrains/kotlin)
- [kotlinx-coroutines](https://github.com/Kotlin/kotlinx.coroutines) - [kotlinx-coroutines](https://github.com/Kotlin/kotlinx.coroutines)
- [kotlinx-io](https://github.com/Kotlin/kotlinx-io) - [kotlinx-io](https://github.com/Kotlin/kotlinx-io)
- [kotlin-reflect](https://github.com/JetBrains/kotlin) - [kotlin-reflect](https://github.com/JetBrains/kotlin)
- [pcap4j](https://github.com/kaitoy/pcap4j)
- [atomicfu](https://github.com/Kotlin/kotlinx.atomicfu) - [atomicfu](https://github.com/Kotlin/kotlinx.atomicfu)
- [ktor](https://github.com/ktorio/ktor) - [ktor](https://github.com/ktorio/ktor)
- [tornadofx](https://github.com/edvin/tornadofx)
- [javafx](https://github.com/openjdk/jfx)
- [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization) - [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization)
- [bouncycastle](https://www.bouncycastle.org/java.html) - [bouncycastle](https://www.bouncycastle.org/java.html)
## License
## 许可证
协议原版权归属腾讯科技股份有限公司所有,本项目其他代码遵守: 协议原版权归属腾讯科技股份有限公司所有,本项目其他代码遵守:
**GNU AFFERO GENERAL PUBLIC LICENSE version 3** **GNU AFFERO GENERAL PUBLIC LICENSE version 3**
其中部分要求: 其中部分要求:
- (见 LICENSE 第 13 节) 尽管本许可协议有其他规定,但如果您修改本程序,则修改后的版本必须显着地为所有通过计算机网络与它进行远程交互的用户(如果您的版本支持这种交互)提供从网络服务器通过一些标准或惯用的软件复制方法**免费**访问相应的**源代码**的机会 - (见 LICENSE 第 13 节) 尽管本许可协议有其他规定,但如果您修改本程序,则修改后的版本必须显着地为所有通过计算机网络与它进行远程交互的用户(如果您的版本支持这种交互)提供从网络服务器通过一些标准或惯用的软件复制方法**免费**访问相应的**源代码**的机会
- (见 LICENSE 第 4 节) 您可以免费或收费地传递这个项目的源代码或目标代码(即编译结果), **但前提是提供明显的版权声明** (您需要标注本 `GitHub` 项目地址) - (见 LICENSE 第 4 节) 您可以免费或收费地传递这个项目的源代码或目标代码(即编译结果), **但前提是提供明显的版权声明** (您需要标注本 `GitHub` 项目地址)
## Acknowledgement ------
特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 的授权
[<img src=".github/jetbrains-variant-3.png" width="200"/>](https://www.jetbrains.com/?from=mirai) Copyright (C) 2019-2020 mamoe and Mirai contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.

View File

@ -1,48 +0,0 @@
buildscript {
repositories {
mavenLocal()
jcenter()
mavenCentral()
google()
maven { url 'https://dl.bintray.com/kotlin/kotlin-dev/'}
}
dependencies {
// Do try to waste your time.
classpath 'com.android.tools.build:gradle:3.5.3'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath("com.github.jengelman.gradle.plugins:shadow:5.2.0")
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion"
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicFuVersion"
}
}
try {
def keyProps = new Properties()
def keyFile = file("local.properties")
if (keyFile.exists()) keyFile.withInputStream { keyProps.load(it) }
if (!keyProps.getProperty("sdk.dir", "").isEmpty()) {
project.ext.set("isAndroidSDKAvailable", true)
} else {
project.ext.set("isAndroidSDKAvailable", false)
}
}catch(Exception e){}
allprojects {
group = "net.mamoe"
version = getProperty("mirai_version")
// tasks.withType(KotlinCompile).all { task ->
// task.kotlinOptions{
// jvmTarget = '1.6'
// }
// }
repositories {
mavenLocal()
jcenter()
mavenCentral()
google()
maven { url "https://dl.bintray.com/kotlin/kotlin-eap" }
maven { url "https://dl.bintray.com/kotlin/kotlin-dev" }
}
}

49
build.gradle.kts Normal file
View File

@ -0,0 +1,49 @@
import java.lang.System.getProperty
import java.util.*
buildscript {
repositories {
mavenLocal()
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter()
// mavenCentral()
google()
// maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
}
dependencies {
val kotlinVersion: String by project
val atomicFuVersion: String by project
classpath("com.android.tools.build:gradle:3.5.3")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
classpath("com.github.jengelman.gradle.plugins:shadow:5.2.0")
classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion")
classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicFuVersion")
}
}
runCatching {
val keyProps = Properties().apply {
file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) }
}
if (keyProps.getProperty("sdk.dir", "").isNotEmpty()) {
project.ext.set("isAndroidSDKAvailable", true)
} else {
project.ext.set("isAndroidSDKAvailable", false)
}
}
allprojects {
group = "net.mamoe"
version = getProperty("miraiVersion")
repositories {
mavenLocal()
maven(url = "https://mirrors.huaweicloud.com/repository/maven")
jcenter()
// mavenCentral()
google()
// maven (url="https://dl.bintray.com/kotlin/kotlin-eap")
}
}

View File

@ -0,0 +1,186 @@
# Mirai Guide - Build For Mirai
由于Mirai项目在快速推进中因此内容时有变动本文档的最后更新日期为```2020-02-29```,对应版本```0.23.0```
本页面采用Kotlin作为开发语言**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt)
本页面是[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md)的后续Guide
## build.gradle
我们首先来看一下完整的```build.gradle```文件
```groovy
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
}
group 'test'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation 'net.mamoe:mirai-core-qqandroid-jvm:0.19.1'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
```
使用gradle直接打包不会将依赖也打包进去
因此,我们引入一些插件进行打包
### ShadowJar
shadowJar支持将依赖也打包到Jar内下面介绍用法。
#### 1.buildscript
首先声明buildScript
```groovy
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
}
}
```
在plugin前加入以上语句
#### 2.在plugins中进行插件的使用
将原本的plugins
```groovy
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
}
```
覆盖为
```groovy
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
id 'com.github.johnrengelman.shadow' version '5.2.0'//使用shadow对依赖进行打包
}
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'java'
```
#### 3.添加shadowJar
在文件底部添加
```groovy
shadowJar {
// 生成包的命名规则: baseName-version-classifier.jar
manifest {
attributes(
'Main-Class': 'net.mamoe.mirai.simpleloader.MyLoaderKt'//入口点
)
}
// 将 build.gradle 打入到 jar 中, 方便查看依赖包版本
from("./"){
include 'build.gradle'
}
}
```
#### 4.运行build
在IDEA中点击```ShadowJar```左侧的run按钮(绿色小三角)build完成后在```build\libs```中找到jar
至此build.gradle内的内容是
```groovy
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.github.jengelman.gradle.plugins:shadow:5.2.0'
}
}
plugins {
id 'java'
id 'org.jetbrains.kotlin.jvm' version '1.3.61'
id 'com.github.johnrengelman.shadow' version '5.2.0'//使用shadow对依赖进行打包
}
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'java'
group 'test'
version '1.0-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation 'net.mamoe:mirai-core-qqandroid-jvm:0.23.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
shadowJar {
// 生成包的命名规则: baseName-version-classifier.jar
manifest {
attributes(
'Main-Class': 'net.mamoe.mirai.simpleloader.MyLoaderKt'
)
}
// 将 build.gradle 打入到 jar 中, 方便查看依赖包版本
from("./"){
include 'build.gradle'
}
}
```

View File

@ -0,0 +1,126 @@
# Mirai Guide - Getting Started
由于Mirai项目在快速推进中因此内容时有变动本文档的最后更新日期为```2020-02-29```,对应版本```0.23.0```
假如仅仅使用Mirai不需要对整个项目进行Clone只需在项目内添加Gradle Dependency或使用即可。
下面介绍详细的入门步骤。
本页采用Kotlin作为开发语言**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt)
## 起步步骤
通过编写Kotlin程序以第三方库的形式调用```mirai-core```并定义你的Mirai Bot行为。
假如已经对Gradle有一定了解可跳过12
### 1 安装IDEA与JDK
JDK要求6以上
### 2 新建Gradle项目
*使用gradle项目可能需要代理在IDEA的```settings```->```proxy settings```中可以设置
- 在```File->new project```中选择```Gradle```
- 在面板中的```Additional Libraries and Frameworks```中勾选```Java```以及```Kotlin/JVM```
- 点击```next```,填入```GroupId```与```ArtifactId```(对于测试项目来说,可随意填写)
- 点击```next```,点击```Use default gradle wrapper(recommended)```
- 创建项目完成
### 3 添加依赖
- 打开项目的```Project```面板,点击编辑```build.gradle```
- 首先添加repositories
```groovy
//添加jcenter仓库
/*
repositories {
mavenCentral()
}
原文内容,更新为下文
*/
repositories {
mavenCentral()
jcenter()
}
```
- 添加依赖将dependencies部分覆盖。 `mirai-core` 的最新版本为: [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
```groovy
dependencies {
implementation 'net.mamoe:mirai-core-qqandroid-jvm:0.23.0'//此处版本应替换为当前最新
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
```
- 打开右侧Gradle面板点击刷新按钮
- 至此,依赖添加完成
### 4 Try Bot
- 在src/main文件夹下新建文件夹命名为```kotlin```
- 在```kotlin```下新建包(在```kotlin```文件夹上右键-```New```-```Package```) 包名为```net.mamoe.mirai.simpleloader```
- 在包下新建kotlin文件```MyLoader.kt```
```kotlin
package net.mamoe.mirai.simpleloader
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.event.subscribeMessages
suspend fun main() {
val qqId = 10000L//Bot的QQ号需为Long类型在结尾处添加大写L
val password = "your_password"//Bot的密码
val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录
miraiBot.subscribeMessages {
"你好" reply "你好!"
case("at me") {
reply(sender.at() + " 给爷爬 ")
}
(contains("舔") or contains("刘老板")) {
"刘老板太强了".reply()
}
}
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
}
```
- 单击编辑器内第8行(```suspend fun main```)左侧的run按钮(绿色三角)等待MiraiBot成功登录。
- 本例的功能中在任意群内任意成员发送包含“舔”字或“刘老板”字样的消息MiraiBot会回复“刘老板太强了”
至此简单的入门已经结束下面可根据不同的需求参阅wiki进行功能的添加。
下面,可以尝试对不同事件进行监听[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md)
### 此外还可以使用Maven作为包管理工具
本项目推荐使用gradle因此不提供详细入门指导
```xml
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-core-qqandroid-jvm</artifactId>
<version>0.23.0</version> <!-- 替换版本为最新版本 -->
</dependency>
</dependencies>
```

96
docs/guide_quick_start.md Normal file
View File

@ -0,0 +1,96 @@
# Mirai Guide - Quick Start
由于 mirai 项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020/3/1```,对应版本```0.23.0```
本文适用于对 Kotlin 较熟悉的开发者,
使用 mirai 作为第三方依赖库引用任意一个 Kotlin, Java 或其他 JVM 平台的项目
**若你希望一份更基础的教程**, 请参阅: [mirai-guide-getting-started](guide_getting_started.md)
**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt)
## 构建需求
- Kotlin 1.3.61 (必须)
- JDK 6 或更高 (必须)
- Android SDK 29 (可选, 用于编译安卓目标)
## 获取 Demo
可在 [mirai-demos](https://github.com/mamoe/mirai-demos) 中获取已经配置好依赖的示例项目.
## Quick Start
请将 `VERSION` 替换为 `mirai-core` 的最新版本号(如 `0.23.0`):
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
### 添加依赖
#### Maven
Kotlin 在 Maven 上只支持 JVM 平台.
```xml
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-core-qqandroid-jvm</artifactId>
<version>0.23.0</version> <!-- 替换版本为最新版本 -->
</dependency>
</dependencies>
```
#### Gradle
Mirai 只发布在 `jcenter`, 因此请确保添加 `jcenter()` 仓库:
```kotlin
repositories{
jcenter()
}
```
**注意:**
Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。
只添加 API 模块将无法正常工作。
现在只推荐使用 QQAndroid 协议,请参照下文选择对应目标平台的依赖添加。
**jvm** (JVM 平台源集)
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-jvm:VERSION")
```
**common** (Kotlin 多平台项目的通用源集)
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-common:VERSION")
```
**android** (Android 平台源集)
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-android:VERSION")
```
### 开始使用
```kotlin
val bot = Bot(qqId, password).alsoLogin()
bot.subscribeMessages {
"你好" reply "你好!"
contains("图片"){ File("C:\\image.png").sendAsImage() }
}
bot.subscribeAlways<MemberPermissionChangedEvent> {
if (it.kind == BECOME_OPERATOR)
reply("${it.member.id} 成为了管理员")
}
```

View File

@ -0,0 +1,116 @@
# Mirai Guide - Subscribe Events
由于Mirai项目在快速推进中因此内容时有变动本文档的最后更新日期为```2020-02-29```,对应版本```0.23.0```
本页面采用Kotlin作为开发语言**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt)
本页面是[Mirai Guide - Getting Started](/docs/guide_getting_started.md)的后续Guide
## 消息事件-Message Event
首先我们来回顾上一个Guide的源码
```kotlin
suspend fun main() {
val qqId = 10000L//Bot的QQ号需为Long类型在结尾处添加大写L
val password = "your_password"//Bot的密码
val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录
miraiBot.subscribeMessages {
"你好" reply "你好!"
case("at me") {
reply(sender.at() + " 给爷爬 ")
}
(contains("舔") or contains("刘老板")) {
"刘老板太强了".reply()
}
}
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
}
```
在本例中,```miraiBot```是一个Bot对象让其登录然后对```Message Event```进行了监听。
对于``````Message Event`````````Mirai```提供了较其他Event更强大的[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140),本例也采用了[MessageSubscribersBuilder](https://github.com/mamoe/mirai/wiki/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt#L140)。其他具体使用方法可以参考[Wiki:Message Event](https://github.com/mamoe/mirai/wiki/Development-Guide---Kotlin#Message-Event)部分。
## 事件-Event
上一节中提到的```Message Event```仅仅是众多```Event```的这一种,其他```Event```有群员加入群,离开群,私聊等等...
具体doc暂不提供现可翻阅源码[**BotEvents.kt**](https://github.com/mamoe/mirai/blob/master/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt),查看注释。当前事件仍在扩充中,可能有一定不足。
下面我们开始示例对一些事件进行监听。
## 尝试监听事件-Try Subscribing Events
### 监听加群事件
在代码中的```miraiBot.join()```前添加
```kotlin
miraiBot.subscribeAlways<MemberJoinEvent> {
it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!")
}
```
本段语句监听了加入群的事件。
### 监听禁言事件
在代码中添加
```kotlin
miraiBot.subscribeAlways<MemberMuteEvent> (){
it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")
}
```
在被禁言后Bot将发送恭喜语句。
### 添加后的可执行代码
至此,当前的代码为
```kotlin
package net.mamoe.mirai.simpleloader
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.events.MemberJoinEvent
import net.mamoe.mirai.event.events.MemberMuteEvent
import net.mamoe.mirai.event.subscribeAlways
suspend fun main() {
val qqId = 10000L//Bot的QQ号需为Long类型在结尾处添加大写L
val password = "your_password"//Bot的密码
val miraiBot = Bot(qqId, password).alsoLogin()//新建Bot并登录
miraiBot.subscribeMessages {
"你好" reply "你好!"
case("at me") {
reply(sender.at() + " 给爷爬 ")
}
(contains("舔") or contains("刘老板")) {
"刘老板太强了".reply()
}
}
miraiBot.subscribeAlways<MemberJoinEvent> {
it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!")
}
miraiBot.subscribeAlways<MemberMuteEvent> (){
it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份")
}
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
}
```
下面可以参阅[Mirai Guide - Build For Mirai](/docs/guide_build_for_mirai.md)对你的Mirai应用进行打包

View File

@ -1,20 +1,16 @@
# style guide # style guide
kotlin.code.style=official kotlin.code.style=official
# config # config
mirai_version=0.15.0 miraiVersion=0.27.0
mirai_japt_version=1.0.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 kotlinXIoVersion=0.1.16
coroutinesIoVersion=0.1.16 coroutinesIoVersion=0.1.16
# utility # utility
ktorVersion=1.3.1 ktorVersion=1.3.1
klockVersion=1.7.0
# gradle plugin
protobufJavaVersion=3.10.0

104
gradle/publish-japt.gradle Normal file
View File

@ -0,0 +1,104 @@
// kotlinx.coroutines
def pomConfig = {
licenses {
license {
name "AGPL-V3"
url "https://www.gnu.org/licenses/agpl-3.0.txt"
distribution "repo"
}
}
developers {
developer {
id "mamoe"
name "Mamoe Technologies"
}
}
scm {
url "https://github.com/mamoe/mirai"
}
}
bintray {
def keyProps = new Properties()
def keyFile = file("../keys.properties")
if (keyFile.exists()) keyFile.withInputStream { keyProps.load(it) }
user = keyProps.getProperty("bintrayUser")
key = keyProps.getProperty("bintrayKey")
pkg {
repo = 'mirai'
name = "mirai-japt"
licenses = ['AGPL']
vcsUrl = 'https://github.com/mamoe/mirai'
}
}
afterEvaluate {
project.publishing.publications.forEach { publication ->
publication.pom.withXml {
def root = asNode()
//root.appendNode('groupId', project.group)
//root.appendNode('artifactId', project.name)
//root.appendNode('version', project.version)
root.appendNode('name', project.name)
root.appendNode('description', project.description)
root.appendNode('url', 'https://github.com/mamoe/mirai')
root.children().last() + pomConfig
}
}
}
bintrayUpload.doFirst {
publications = project.publishing.publications
}
bintrayUpload.dependsOn {
def list = new LinkedList<Task>()
list.add(tasks.getByName("build"))
list.addAll(tasks.findAll { task -> task.name.contains('Jar') })
list.addAll(tasks.findAll { task -> task.name.startsWith('generateMetadataFileFor') })
list.addAll(tasks.findAll { task -> task.name.startsWith('generatePomFileFor') })
list
}
// empty xxx-javadoc.jar
task javadocJar(type: Jar) {
archiveClassifier = 'javadoc'
}
publishing {
publications.all {
// add empty javadocs (no need for MPP root publication which publishes only pom file)
if (it.name != 'kotlinMultiplatform') {
it.artifact(javadocJar)
}
// Rename MPP artifacts for backward compatibility
def type = it.name
switch (type) {
case 'kotlinMultiplatform':
it.artifactId = "$project.name"
break
case 'metadata':
it.artifactId = "$project.name-common"
break
case 'jvm':
it.artifactId = "$project.name"
break
case 'js':
case 'native':
it.artifactId = "$project.name-$type"
break
}
// disable metadata everywhere, but in native modules
if (type == 'maven' || type == 'metadata' || type == 'jvm' || type == 'js') {
moduleDescriptorGenerator = null
}
}
}

View File

@ -1,4 +1,5 @@
// kotlinx.coroutines // kotlinx.coroutines
// Source code from kotlinx.coroutines
def pomConfig = { def pomConfig = {
licenses { licenses {
@ -12,6 +13,7 @@ def pomConfig = {
developer { developer {
id "mamoe" id "mamoe"
name "Mamoe Technologies" name "Mamoe Technologies"
email "support@mamoe.net"
} }
} }
scm { scm {
@ -65,12 +67,16 @@ bintrayUpload.dependsOn {
list list
} }
try{
// empty xxx-javadoc.jar // empty xxx-javadoc.jar
task javadocJar(type: Jar) { task javadocJar(type: Jar) {
archiveClassifier = 'javadoc' archiveClassifier = 'javadoc'
} }
} catch (Exception e){
}
publishing { publishing {
publications.all { publications.all {
// add empty javadocs (no need for MPP root publication which publishes only pom file) // add empty javadocs (no need for MPP root publication which publishes only pom file)
@ -88,7 +94,7 @@ publishing {
it.artifactId = "$project.name-common" it.artifactId = "$project.name-common"
break break
case 'jvm': case 'jvm':
it.artifactId = "$project.name" it.artifactId = "$project.name-jvm"
break break
case 'js': case 'js':
case 'native': case 'native':

View File

@ -1,5 +1,5 @@
#Thu Feb 06 14:10:33 CST 2020 #Thu Feb 06 14:10:33 CST 2020
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

View File

@ -1,58 +0,0 @@
# mirai-api-http
<b>
Mirai-API-http provides adapter for ALL langugae to access mirai via HTTP protocol.<br>
</b>
**[中文](README_CH.md)**
### Start Session-Authorize
```php
Path: /auth
Method: POST
```
this verify your session to one bot and you could have full access to that bot<br>
NOTE that only 1 bot could be control under 1 session, you could have multiple session to control all bots.
#### Request:<br>
| name | type | optional|example|note|
| --- | --- | --- | --- | --- |
| key | String |false|U9HSaDXl39ksd918273hU|MIRAI API HTTP key, this could be found after initialize|
| qq | String |false|1040400290|bot QQ number you want to access|
#### Response if success:<br>
| name | type | example|note|
| --- | --- | --- | --- |
| success |Boolean |true|if this session is authorized|
| session |String |UANSHDKSLAOISN|your session key|
#### Response if failed:<br>
| name | type | example|note|
| --- | --- | --- | --- |
| success |Boolean |false|if this session is authorized|
| session |String |null|your session key|
| error |int |0|error code|
#### Error:<br>
| code | reason|
| --- | --- |
| 0 | wrong MIRAI API HTTP key |
| 1 | unknown bot number |
without session key, you are not able to access any method below.</br>
session key should be attached to your <b>cookies</b> like this:
| name | value |
| --- | --- |
| session |your session key here |
if you were getting HTTP error code 403, you should ask for a new session key.

View File

@ -1,882 +0,0 @@
# mirai-api-http
<b>Mirai-API-http 提供HTTP API供所有语言使用mirai</b>
## 快速开始
```kotlin
fun main() {
val bot = Bot(123456789, "password")
bot.login()
MiraiHttpAPIServer.start()
bot.network.awaitDisconnection()
}
```
## 认证相关
### 开始会话-认证(Authorize)
```
[POST] /auth
```
使用此方法验证你的身份,并返回一个会话
#### 请求:
```json5
{
"authKey": "U9HSaDXl39ksd918273hU"
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------- | ------ | ----- | ----------------------- | ---------------------------------------------------------- |
| authKey | String | false | "U9HSaDXl39ksd918273hU" | 创建Mirai-Http-Server时生成的key可在启动时指定或随机生成 |
#### 响应: 返回(成功):
```json5
{
"code": 0,
"session": "UnVerifiedSession"
}
```
| 名字 | 类型 | 举例 | 说明 |
| ------- | ------ | ------------------- | --------------- |
| code | Int | 0 | 返回状态码 |
| session | String | "UnVerifiedSession" | 你的session key |
#### 状态码:
| 代码 | 原因 |
| ---- | ----------------------------- |
| 0 | 正常 |
| 1 | 错误的MIRAI API HTTP auth key |
session key 是使用以下方法必须携带的
session key 使用前必须进行校验和绑定指定的Bot**每个Session只能绑定一个Bot但一个Bot可有多个Session**
session Key 在未进行校验的情况下,一定时间后将会被自动释放
### 校验Session
```
[POST] /verify
```
使用此方法校验并激活你的Session同时将Session与一个**已登录**的Bot绑定
#### 请求:
```json5
{
"sessionKey": "UnVerifiedSession",
"qq": 123456789
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ---------- | ------ | ----- | ------------------- | -------------------------- |
| sessionKey | String | false | "UnVerifiedSession" | 你的session key |
| qq | Long | false | 123456789 | Session将要绑定的Bot的qq号 |
#### 响应: 返回统一状态码(后续不再赘述)
```json5
{
"code": 0,
"msg": "success"
}
```
| 状态码 | 原因 |
| ------ | ----------------------------------- |
| 0 | 正常 |
| 1 | 错误的auth key |
| 2 | 指定的Bot不存在 |
| 3 | Session失效或不存在 |
| 4 | Session未认证(未激活) |
| 5 | 发送消息目标不存在(指定对象不存在) |
| 10 | 无操作权限指Bot没有对应操作的限权 |
| 400 | 错误的访问,如参数错误等 |
### 释放Session
```
[POST] /release
```
使用此方式释放session及其相关资源Bot不会被释放
**不使用的Session应当被释放否则Session持续保存Bot收到的消息将会导致内存泄露**
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"qq": 123456789
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ---------- | ------ | ----- | -----------------| -------------------------- |
| sessionKey | String | false | "YourSessionKey" | 你的session key |
| qq | Long | false | 123456789 | 与该Session绑定Bot的QQ号码 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
> SessionKey与Bot 对应错误时将会返回状态码5指定对象不存在
## 消息相关
### 发送好友消息
```
[POST] /sendFriendMessage
```
使用此方法向指定好友发送消息
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 发送消息目标好友的QQ号 |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 发送群消息
```
[POST] /sendGroupMessage
```
使用此方法向指定群发送消息
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 发送消息目标群的群号 |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 发送引用回复消息(仅支持群消息)
```
[POST] /sendQuoteMessage
```
使用此方法向指定的消息进行引用回复
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"messageChain": [
{ "type": "Plain", "text":"hello\n" },
{ "type": "Plain", "text":"world" }
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | -------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | false | 987654321 | 引用消息的Message Source的Uid |
| messageChain | Array | false | [] | 消息链,是一个消息对象构成的数组 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 发送图片消息通过URL
```
[POST] /sendGroupMessage
```
使用此方法向指定群发送消息
#### 请求
```json5
{
"sessionKey": "YourSession",
"target": 987654321,
"qq": 1234567890,
"group": 987654321,
"urls": [
"https://xxx.yyy.zzz/",
"https://aaa.bbb.ccc/"
]
}
```
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | ---------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| target | Long | true | 987654321 | 发送对象的QQ号或群号可能存在歧义 |
| qq | Long | true | 123456789 | 发送对象的QQ号 |
| group | Long | true | 987654321 | 发送对象的群号 |
| urls | Array | false | [] | 是一个url字符串构成的数组 |
#### 响应: 图片的imageId数组
```json5
[
"{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.jpg",
"{YYYYYYYY-YYYY-YYYY-YYYY-YYYYYYYYYYYY}.jpg"
]
```
### 图片文件上传
```
[POST] /sendGroupMessage
```
使用此方法上传图片文件至服务器并返回ImageId
#### 请求
Content-Typemultipart/form-data
| 名字 | 类型 | 可选 | 举例 | 说明 |
| ------------ | ------ | ----- | ----------- | ---------------------------------- |
| sessionKey | String | false | YourSession | 已经激活的Session |
| type | String | false | "friend " | "friend" 或 "group" |
| img | File | false | - | 图片文件 |
#### 响应: 图片的imageId好友图片与群聊图片Id不同
```
{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}.jpg
```
### 获取Bot收到的消息
```
[GET] /fetchMessage?sessionKey=YourSessionKey&count=10
```
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | --------------- |
| sessionKey | false | YourSessionKey | 你的session key |
| count | false | 10 | 获取消息的数量 |
#### 响应: 返回JSON对象
```json5
[{
"type": "GroupMessage", // 消息类型GroupMessage或FriendMessage
"messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Source",
"uid": 123456
},{
"type": "Plain",
"text": "Miral牛逼"
}],
"sender": { // 发送者信息
"id": 123456789, // 发送者的QQ号码
"memberName": "化腾", // 发送者的群名片
"permission": "MEMBER", // 发送者的群限权OWNER、ADMINISTRATOR或MEMBER
"group": { // 消息发送群的信息
"id": 1234567890, // 发送群的群号
"name": "Miral Technology", // 发送群的群名称
"permission": "MEMBER" // 发送群中Bot的群限权
}
}
},
{
"type": "FriendMessage", // 消息类型GroupMessage或FriendMessage
"messageChain": [{ // 消息链,是一个消息对象构成的数组
"type": "Plain",
"text": "Miral牛逼"
}],
"sender": { // 发送者信息
"id": 1234567890, // 发送者的QQ号码
"nickName": "", // 发送者的昵称
"remark": "" // 发送者的备注
}
}]
```
### 消息类型一览
#### 消息是构成消息链的基本对象,目前支持的消息类型有
+ [x] At@消息
+ [x] AtAll@全体成员
+ [x] Face表情消息
+ [x] Plain文字消息
+ [x] Image图片消息
+ [ ] XmlXml卡片消息
+ [ ] 敬请期待
#### Source
```json5
{
"type": "Source",
"uid": 123456
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ------------------------------------------------------------ |
| uid | Long | 消息的识别号用于引用回复Source类型只在群消息中返回且永远为chain的第一个元素 |
#### At
```json5
{
"type": "At",
"target": 123456,
"display": "@Mirai"
}
```
| 名字 | 类型 | 说明 |
| ------- | ------ | ------------------------- |
| target | Long | 群员QQ号 |
| display | String | @时显示的文本如"@Mirai" |
#### AtAll
```json5
{
"type": "AtAll"
}
```
| 名字 | 类型 | 说明 |
| ------- | ------ | ------------------------- |
| - | - | - |
#### Face
```json5
{
"type": "Face",
"faceId": 123
}
```
| 名字 | 类型 | 说明 |
| ------ | ---- | ---------- |
| faceId | Int | QQ表情编号 |
#### Plain
```json5
{
"type": "Plain",
"text": "Mirai牛逼"
}
```
| 名字 | 类型 | 说明 |
| ---- | ------ | -------- |
| text | String | 文字消息 |
#### Image
```json5
{
"type": "Image"
// 暂时不支持Image
}
```
| 名字 | 类型 | 说明 |
| ---- | ---- | ---- |
| | | |
#### Xml
```json5
{
"type": "Xml",
"xml": "XML"
}
```
| 名字 | 类型 | 说明 |
| ---- | ------ | ------- |
| xml | String | XML文本 |
## 管理相关
### 获取好友列表
使用此方法获取bot的好友列表
```
[GET] /friendList?sessionKey=YourSessionKey
```
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | --------------- |
| sessionKey | false | YourSessionKey | 你的session key |
#### 响应: 返回JSON对象
```json5
[{
"id":123456789,
"nickName":"",
"remark":""
},{
"id":987654321,
"nickName":"",
"remark":""
}]
```
### 获取群列表
使用此方法获取bot的群列表
```
[GET] /groupList?sessionKey=YourSessionKey
```
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | --------------- |
| sessionKey | false | YourSessionKey | 你的session key |
#### 响应: 返回JSON对象
```json5
[{
"id":123456789,
"name":"群名1",
"permission": "MEMBER"
},{
"id":987654321,
"name":"群名2",
"permission": "MEMBER"
}]
```
### 获取群成员列表
使用此方法获取bot指定群种的成员列表
```
[GET] /memberList?sessionKey=YourSessionKey
```
#### 请求:
| 名字 | 可选 | 举例 | 说明 |
| ---------- | ----- | -------------- | --------------- |
| sessionKey | false | YourSessionKey | 你的session key |
| target | false | 123456789 | 指定群的群号 |
#### 响应: 返回JSON对象
```json5
[{
"id":1234567890,
"memberName":"",
"permission":"MEMBER",
"group":{
"id":12345,
"name":"群名1",
"permission": "MEMBER"
}
},{
"id":9876543210,
"memberName":"",
"permission":"OWNER",
"group":{
"id":54321,
"name":"群名2",
"permission": "MEMBER"
}
}]
```
### 群全体禁言
使用此方法令指定群进行全体禁言(需要有相关限权)
```
[POST] /muteAll
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ---------- | ----- | ------ | ---------------- | --------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 群解除全体禁言
使用此方法令指定群解除全体禁言(需要有相关限权)
```
[POST] /unmuteAll
```
#### 请求:
同全体禁言
#### 响应
同全体禁言
### 群禁言群成员
使用此方法指定群禁言指定群员(需要有相关限权)
```
[POST] /mute
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321,
"time": 1800
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ---------- | ----- | ------ | ---------------- | ------------------------------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| memberId | false | Long | 987654321 | 指定群员QQ号 |
| time | true | Int | 1800 | 禁言时长单位为秒最多30天默认为0 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 群解除群成员禁言
使用此方法令指定群解除全体禁言(需要有相关限权)
```
[POST] /unmute
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"memberId": 987654321
}
```
#### 响应
同群禁言群成员
### 移除群成员
使用此方法移除指定群成员(需要有相关限权)
```
[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
```
#### 请求:
```json5
{
"sessionKey": "YourSessionKey",
"target": 123456789,
"config": {
"name": "群名称",
"announcement": "群公告",
"confessTalk": true,
"allowMemberInvite": true,
"autoApprove": true,
"anonymousChat": true
}
}
```
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | "YourSessionKey" | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
| config | false | Object | {} | 群设置 |
| name | true | String | "Name" | 群名 |
| announcement | true | Boolean | true | 群公告 |
| confessTalk | true | Boolean | true | 是否开启坦白说 |
| allowMemberInvite | true | Boolean | true | 是否运行群员邀请 |
| autoApprove | true | Boolean | true | 是否开启自动审批入群 |
| anonymousChat | true | Boolean | true | 是否允许匿名聊天 |
#### 响应: 返回统一状态码
```json5
{
"code": 0,
"msg": "success"
}
```
### 获取群设置
使用此方法获取群设置
```
[Get] /groupConfig?sessionKey=YourSessionKey&target=123456789
```
#### 请求:
| 名字 | 可选 | 类型 | 举例 | 说明 |
| ----------------- | ----- | ------- | ---------------- | -------------------- |
| sessionKey | false | String | YourSessionKey | 你的session key |
| target | false | Long | 123456789 | 指定群的群号 |
#### 响应
```json5
{
"name": "群名称",
"announcement": "群公告",
"confessTalk": true,
"allowMemberInvite": true,
"autoApprove": 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

@ -1,75 +0,0 @@
@file:Suppress("UNUSED_VARIABLE")
import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler
plugins {
id("kotlinx-atomicfu")
kotlin("jvm")
id("kotlinx-serialization")
}
group = "net.mamoe.mirai"
version = rootProject.ext["mirai_version"].toString()
description = "Mirai Http Api"
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun KotlinDependencyHandler.kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun KotlinDependencyHandler.ktor(id: String, version: String = ktorVersion) = "io.ktor:ktor-$id:$version"
kotlin {
sourceSets["main"].apply {
dependencies {
implementation(project(":mirai-core-qqandroid"))
implementation(kotlin("stdlib-jdk8", kotlinVersion))
implementation(kotlin("stdlib-jdk7", kotlinVersion))
implementation(kotlin("reflect", kotlinVersion))
implementation(ktor("server-cio"))
implementation(kotlinx("io-jvm", kotlinXIoVersion))
implementation(ktor("http-jvm"))
implementation("org.slf4j:slf4j-simple:1.7.26")
}
}
sourceSets["test"].apply {
dependencies {
}
kotlin.outputDir = file("build/classes/kotlin/jvm/test")
kotlin.setSrcDirs(listOf("src/$name/kotlin"))
}
sourceSets.all {
languageSettings.enableLanguageFeature("InlineClasses")
languageSettings.useExperimentalAnnotation("kotlin.Experimental")
dependencies {
implementation(kotlin("stdlib", kotlinVersion))
implementation(kotlin("serialization", kotlinVersion))
implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
implementation(kotlinx("io", kotlinXIoVersion))
implementation(kotlinx("coroutines-io", coroutinesIoVersion))
implementation(kotlinx("coroutines-core", coroutinesVersion))
implementation(kotlinx("serialization-runtime", serializationVersion))
implementation(ktor("server-core"))
implementation(ktor("http"))
}
}
}

View File

@ -1,62 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http
import io.ktor.application.Application
import io.ktor.server.cio.CIO
import io.ktor.server.engine.applicationEngineEnvironment
import io.ktor.server.engine.connector
import io.ktor.server.engine.embeddedServer
import io.ktor.util.KtorExperimentalAPI
import net.mamoe.mirai.api.http.route.mirai
import net.mamoe.mirai.utils.DefaultLogger
import org.slf4j.LoggerFactory
import org.slf4j.helpers.NOPLogger
import org.slf4j.helpers.NOPLoggerFactory
object MiraiHttpAPIServer {
private val logger = DefaultLogger("Mirai HTTP API")
init {
SessionManager.authKey = generateSessionKey()//用于验证的key, 使用和SessionKey相同的方法生成, 但意义不同
}
fun setAuthKey(key: String) {
SessionManager.authKey = key
}
@UseExperimental(KtorExperimentalAPI::class)
fun start(
port: Int = 8080,
authKey: String,
callback: (() -> Unit)? = null
) {
require(authKey.length in 8..128) { "Expected authKey length is between 8 to 128" }
SessionManager.authKey = authKey
// TODO: start是无阻塞的理应获取启动状态后再执行后续代码
try {
embeddedServer(CIO, environment = applicationEngineEnvironment {
this.log = NOPLoggerFactory().getLogger("NMYSL")
this.module(Application::mirai)
connector {
this.port = port
}
}).start()
logger.info("Http api server is running with authKey: ${SessionManager.authKey}")
callback?.invoke()
} catch (e: Exception) {
logger.error("Http api server launch error")
}
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http
import kotlinx.coroutines.*
import net.mamoe.mirai.Bot
import net.mamoe.mirai.api.http.queue.MessageQueue
import net.mamoe.mirai.event.Listener
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.MessagePacket
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
tailrec fun generateSessionKey(): String {
fun generateRandomSessionKey(): String {
val all = "QWERTYUIOPASDFGHJKLZXCVBNM1234567890qwertyuiopasdfghjklzxcvbnm"
return buildString(capacity = 8) {
repeat(8) {
append(all.random())
}
}
}
val key = generateRandomSessionKey()
if (!SessionManager.allSession.containsKey(key)) {
return key
}
return generateSessionKey()
}
internal object SessionManager {
val allSession: MutableMap<String, Session> = mutableMapOf()
lateinit var authKey: String
fun createTempSession(): TempSession = TempSession(EmptyCoroutineContext).also { newTempSession ->
allSession[newTempSession.key] = newTempSession
//设置180000ms后检测并回收
newTempSession.launch {
delay(180000)
allSession[newTempSession.key]?.run {
if (this is TempSession)
closeSession(newTempSession.key)
}
}
}
operator fun get(sessionKey: String) = allSession[sessionKey]
fun containSession(sessionKey: String): Boolean = allSession.containsKey(sessionKey)
fun closeSession(sessionKey: String) = allSession.remove(sessionKey)?.also { it.close() }
fun closeSession(session: Session) = closeSession(session.key)
}
/**
* @author NaturalHG
* 这个用于管理不同Client与Mirai HTTP的会话
*
* [Session]均为内部操作用类
* 需使用[SessionManager]
*/
abstract class Session internal constructor(
coroutineContext: CoroutineContext
) : CoroutineScope {
val supervisorJob = SupervisorJob(coroutineContext[Job])
final override val coroutineContext: CoroutineContext = supervisorJob + coroutineContext
val key: String = generateSessionKey()
internal open fun close() {
supervisorJob.complete()
}
}
/**
* 任何新链接建立后分配一个[TempSession]
*
* TempSession在建立180s内没有转变为[AuthedSession]应被清除
*/
class TempSession internal constructor(coroutineContext: CoroutineContext) : Session(coroutineContext)
/**
* 任何[TempSession]认证后转化为一个[AuthedSession]
* 在这一步[AuthedSession]应该已经有assigned的bot
*/
class AuthedSession internal constructor(val bot: Bot, coroutineContext: CoroutineContext) : Session(coroutineContext) {
val messageQueue = MessageQueue()
private val _listener: Listener<MessagePacket<*, *>>
init {
bot.subscribeMessages {
_listener = always { this.run(messageQueue::add) } // this aka messagePacket
}
}
override fun close() {
_listener.complete()
super.close()
}
}

View File

@ -1,38 +0,0 @@
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

@ -1,21 +0,0 @@
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,53 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.Serializable
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI
@Serializable
abstract class ContactDTO : DTO {
abstract val id: Long
}
@Serializable
data class QQDTO(
override val id: Long,
val nickName: String,
val remark: String
) : ContactDTO() {
// TODO: queryProfile.nickname & queryRemark.value not support now
constructor(qq: QQ) : this(qq.id, "", "")
}
@Serializable
data class MemberDTO(
override val id: Long,
val memberName: String,
val permission: MemberPermission,
val group: GroupDTO
) : ContactDTO() {
constructor(member: Member) : this(
member.id, member.groupCardOrNick, member.permission,
GroupDTO(member.group)
)
}
@Serializable
data class GroupDTO(
override val id: Long,
val name: String,
val permission: MemberPermission
) : ContactDTO() {
@UseExperimental(MiraiExperimentalAPI::class)
constructor(group: Group) : this(group.id, group.name, group.botPermission)
}

View File

@ -1,18 +0,0 @@
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,113 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.data.common
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.MiraiInternalAPI
/*
* DTO data class
* */
// MessagePacket
@Serializable
@SerialName("FriendMessage")
data class FriendMessagePacketDTO(val sender: QQDTO) : MessagePacketDTO()
@Serializable
@SerialName("GroupMessage")
data class GroupMessagePacketDTO(val sender: MemberDTO) : MessagePacketDTO()
@Serializable
@SerialName("UnKnownMessage")
data class UnKnownMessagePacketDTO(val msg: String) : MessagePacketDTO()
// Message
@Serializable
@SerialName("Source")
data class MessageSourceDTO(val uid: Long) : MessageDTO()
@Serializable
@SerialName("At")
data class AtDTO(val target: Long, val display: String) : MessageDTO()
@Serializable
@SerialName("AtAll")
data class AtAllDTO(val target: Long = 0) : MessageDTO() // target为保留字段
@Serializable
@SerialName("Face")
data class FaceDTO(val faceId: Int) : MessageDTO()
@Serializable
@SerialName("Plain")
data class PlainDTO(val text: String) : MessageDTO()
@Serializable
@SerialName("Image")
data class ImageDTO(val imageId: String) : MessageDTO()
@Serializable
@SerialName("Xml")
data class XmlDTO(val xml: String) : MessageDTO()
@Serializable
@SerialName("Unknown")
data class UnknownMessageDTO(val text: String) : MessageDTO()
/*
* Abstract Class
* */
@Serializable
sealed class MessagePacketDTO : DTO {
lateinit var messageChain : MessageChainDTO
}
typealias MessageChainDTO = Array<MessageDTO>
@Serializable
sealed class MessageDTO : DTO
/*
Extend function
*/
suspend fun MessagePacket<*, *>.toDTO(): MessagePacketDTO = when (this) {
is FriendMessage -> FriendMessagePacketDTO(QQDTO(sender))
is GroupMessage -> GroupMessagePacketDTO(MemberDTO(sender))
else -> UnKnownMessagePacketDTO("UnKnown Message Packet")
}.apply { messageChain = Array(message.size){ message[it].toDTO() }}
fun MessageChainDTO.toMessageChain() =
MessageChain().apply { this@toMessageChain.forEach { add(it.toMessage()) } }
@UseExperimental(ExperimentalUnsignedTypes::class)
fun Message.toDTO() = when (this) {
is MessageSource -> MessageSourceDTO(messageUid)
is At -> AtDTO(target, display)
is AtAll -> AtAllDTO(0L)
is Face -> FaceDTO(id.value.toInt())
is PlainText -> PlainDTO(stringValue)
is Image -> ImageDTO(imageId)
is XMLMessage -> XmlDTO(stringValue)
else -> UnknownMessageDTO("未知消息类型")
}
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
fun MessageDTO.toMessage() = when (this) {
is AtDTO -> At(target, display)
is AtAllDTO -> AtAll
is FaceDTO -> Face(FaceId(faceId.toUByte()))
is PlainDTO -> PlainText(text)
is ImageDTO -> Image(imageId)
is XmlDTO -> XMLMessage(xml)
is MessageSourceDTO, is UnknownMessageDTO -> PlainText("assert cannot reach")
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.queue
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.MessagePacket
import net.mamoe.mirai.message.data.MessageSource
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedDeque
class MessageQueue : ConcurrentLinkedDeque<MessagePacket<*, *>>() {
val quoteCache = ConcurrentHashMap<Long, GroupMessage>()
fun fetch(size: Int): List<MessagePacket<*, *>> {
var count = size
quoteCache.clear()
val ret = ArrayList<MessagePacket<*, *>>(count)
while (!this.isEmpty() && count-- > 0) {
val packet = pop()
ret.add(packet)
if (packet is GroupMessage) {
addCache(packet)
}
}
return ret
}
private fun addCache(msg: GroupMessage) {
quoteCache[msg.message[MessageSource].messageUid] = msg
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.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.Bot
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.data.NoSuchBotException
import net.mamoe.mirai.api.http.data.StateCode
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import kotlin.coroutines.EmptyCoroutineContext
fun Application.authModule() {
routing {
miraiAuth("/auth") {
if (it.authKey != SessionManager.authKey) {
call.respondStateCode(StateCode(1, "Auth Key错误"))
} else {
call.respondStateCode(StateCode(0, SessionManager.createTempSession().key))
}
}
miraiVerify<BindDTO>("/verify", verifiedSessionKey = false) {
val bot = getBotOrThrow(it.qq)
with(SessionManager) {
closeSession(it.sessionKey)
allSession[it.sessionKey] = AuthedSession(bot, EmptyCoroutineContext)
}
call.respondStateCode(StateCode.Success)
}
miraiVerify<BindDTO>("/release") {
val bot = getBotOrThrow(it.qq)
val session = SessionManager[it.sessionKey] as AuthedSession
if (bot.uin == session.bot.uin) {
SessionManager.closeSession(it.sessionKey)
call.respondStateCode(StateCode.Success)
} else {
throw NoSuchElementException()
}
}
}
}
@Serializable
private data class BindDTO(override val sessionKey: String, val qq: Long) : VerifyDTO()
private fun getBotOrThrow(qq: Long) = try {
Bot.instanceWhose(qq)
} catch (e: NoSuchElementException) {
throw NoSuchBotException
}

View File

@ -1,209 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.CallLogging
import io.ktor.features.DefaultHeaders
import io.ktor.http.ContentType
import io.ktor.http.HttpMethod
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.request.receive
import io.ktor.response.defaultTextContentType
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.routing.route
import io.ktor.util.pipeline.ContextDsl
import io.ktor.util.pipeline.PipelineContext
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.TempSession
import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.AuthDTO
import net.mamoe.mirai.api.http.data.common.DTO
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import net.mamoe.mirai.api.http.util.jsonParseOrNull
import net.mamoe.mirai.api.http.util.toJson
import org.slf4j.Logger
import org.slf4j.helpers.NOPLogger
import org.slf4j.helpers.NOPLoggerFactory
import org.slf4j.impl.SimpleLogger
import org.slf4j.impl.SimpleLoggerFactory
fun Application.mirai() {
install(DefaultHeaders)
install(CallLogging) {
logger = NOPLoggerFactory().getLogger("NMSL")
}
authModule()
messageModule()
infoModule()
groupManageModule()
}
/**
* Auth处理http server的验证
* 为闭包传入一个AuthDTO对象
*/
@ContextDsl
internal fun Route.miraiAuth(
path: String,
body: suspend PipelineContext<Unit, ApplicationCall>.(AuthDTO) -> Unit
): Route {
return route(path, HttpMethod.Post) {
intercept {
val dto = context.receiveDTO<AuthDTO>() ?: throw IllegalParamException("参数格式错误")
this.body(dto)
}
}
}
/**
* Get用于获取bot的属性
* 验证请求参数中sessionKey参数的有效性
*/
@ContextDsl
internal fun Route.miraiGet(
path: String,
body: suspend PipelineContext<Unit, ApplicationCall>.(AuthedSession) -> Unit
): Route {
return route(path, HttpMethod.Get) {
intercept {
val sessionKey = call.parameters["sessionKey"] ?: throw IllegalParamException("参数格式错误")
if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
when(val session = SessionManager[sessionKey]) {
is TempSession -> throw NotVerifiedSessionException
is AuthedSession -> this.body(session)
}
}
}
}
/**
* Verify用于处理bot的行为请求
* 验证数据传输对象(DTO)中是否包含sessionKey字段
* 且验证sessionKey的有效性
*
* @param verifiedSessionKey 是否验证sessionKey是否被激活
*
* it 为json解析出的DTO对象
*/
@ContextDsl
internal inline fun <reified T : VerifyDTO> Route.miraiVerify(
path: String,
verifiedSessionKey: Boolean = true,
crossinline body: suspend PipelineContext<Unit, ApplicationCall>.(T) -> Unit
): Route {
return route(path, HttpMethod.Post) {
intercept {
val dto = context.receiveDTO<T>() ?: throw IllegalParamException("参数格式错误")
SessionManager[dto.sessionKey]?.let {
when {
it is TempSession && verifiedSessionKey -> throw NotVerifiedSessionException
it is AuthedSession -> dto.session = it
}
} ?: throw IllegalSessionException
this.body(dto)
}
}
}
/**
* 统一捕获并处理异常
*/
internal inline fun Route.intercept(crossinline blk: suspend PipelineContext<Unit, ApplicationCall>.() -> Unit) = handle {
try {
blk(this)
} catch (e: IllegalSessionException) {
call.respondStateCode(StateCode.IllegalSession)
} catch (e: NotVerifiedSessionException) {
call.respondStateCode(StateCode.NotVerifySession)
} catch (e: NoSuchBotException) {
call.respondStateCode(StateCode.NoBot)
} catch (e: NoSuchElementException) {
call.respondStateCode(StateCode.NoElement)
} catch (e: PermissionDeniedException) {
call.respondStateCode(StateCode.PermissionDenied)
} catch (e: IllegalAccessException) {
call.respondStateCode(StateCode(400, e.message), HttpStatusCode.BadRequest)
}
}
/*
extend function
*/
internal suspend inline fun <reified T : StateCode> ApplicationCall.respondStateCode(code: T, status: HttpStatusCode = HttpStatusCode.OK) = respondJson(code.toJson(StateCode.serializer()), status)
internal suspend inline fun <reified T : DTO> ApplicationCall.respondDTO(dto: T, status: HttpStatusCode = HttpStatusCode.OK) = respondJson(dto.toJson(), status)
internal suspend fun ApplicationCall.respondJson(json: String, status: HttpStatusCode = HttpStatusCode.OK) =
respondText(json, defaultTextContentType(ContentType("application", "json")), status)
internal suspend inline fun <reified T : DTO> ApplicationCall.receiveDTO(): T? = receive<String>().jsonParseOrNull()
fun PipelineContext<Unit, ApplicationCall>.illegalParam(
expectingType: String?,
paramName: String,
actualValue: String? = call.parameters[paramName]
): Nothing = throw IllegalParamException("Illegal param. A $expectingType is required for `$paramName` while `$actualValue` is given")
@Suppress("IMPLICIT_CAST_TO_ANY")
@UseExperimental(ExperimentalUnsignedTypes::class)
internal inline fun <reified R> PipelineContext<Unit, ApplicationCall>.paramOrNull(name: String): R =
when (R::class) {
Byte::class -> call.parameters[name]?.toByte()
Int::class -> call.parameters[name]?.toInt()
Short::class -> call.parameters[name]?.toShort()
Float::class -> call.parameters[name]?.toFloat()
Long::class -> call.parameters[name]?.toLong()
Double::class -> call.parameters[name]?.toDouble()
Boolean::class -> when (call.parameters[name]) {
"true" -> true
"false" -> false
"0" -> false
"1" -> true
null -> null
else -> illegalParam("boolean", name)
}
String::class -> call.parameters[name]
UByte::class -> call.parameters[name]?.toUByte()
UInt::class -> call.parameters[name]?.toUInt()
UShort::class -> call.parameters[name]?.toUShort()
else -> error(name::class.simpleName + " is not supported")
} as R ?: illegalParam(R::class.simpleName, name)
/**
* multi part
*/
internal fun List<PartData>.value(name: String) =
try {
(filter { it.name == name }[0] as PartData.FormItem).value
} catch (e: Exception) {
throw IllegalParamException("参数格式错误")
}
internal fun List<PartData>.file(name: String) =
try {
filter { it.name == name }[0] as? PartData.FileItem
} catch (e: Exception) {
throw IllegalParamException("参数格式错误")
}

View File

@ -1,11 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route

View File

@ -1,150 +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 kotlinx.serialization.Serializable
import net.mamoe.mirai.api.http.data.PermissionDeniedException
import net.mamoe.mirai.api.http.data.StateCode
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).isMuteAll = true
call.respondStateCode(StateCode.Success)
}
miraiVerify<MuteDTO>("/unmuteAll") {
it.session.bot.getGroup(it.target).isMuteAll = 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.entranceAnnouncement = it }
confessTalk?.let { group.isConfessTalkEnabled = it }
allowMemberInvite?.let { group.isAllowMemberInvite = 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.nameCard = 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.entranceAnnouncement, group.isConfessTalkEnabled, group.isAllowMemberInvite,
group.isAutoApproveEnabled, group.isAnonymousChatEnabled
)
}
@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.nameCard, member.specialTitle)
}

View File

@ -1,39 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.routing.routing
import net.mamoe.mirai.api.http.data.common.GroupDTO
import net.mamoe.mirai.api.http.data.common.MemberDTO
import net.mamoe.mirai.api.http.data.common.QQDTO
import net.mamoe.mirai.api.http.util.toJson
import net.mamoe.mirai.contact.toMutableList
fun Application.infoModule() {
routing {
miraiGet("/friendList") {
val ls = it.bot.qqs.toMutableList().map { qq -> QQDTO(qq) }
call.respondJson(ls.toJson())
}
miraiGet("/groupList") {
val ls = it.bot.groups.toMutableList().map { group -> GroupDTO(group) }
call.respondJson(ls.toJson())
}
miraiGet("/memberList") {
val ls = it.bot.getGroup(paramOrNull("target")).members.toMutableList().map { member -> MemberDTO(member) }
call.respondJson(ls.toJson())
}
}
}

View File

@ -1,117 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.route
import io.ktor.application.Application
import io.ktor.application.call
import io.ktor.http.content.readAllParts
import io.ktor.http.content.streamProvider
import io.ktor.request.receiveMultipart
import io.ktor.response.respondText
import io.ktor.routing.post
import io.ktor.routing.routing
import kotlinx.serialization.Serializable
import net.mamoe.mirai.api.http.AuthedSession
import net.mamoe.mirai.api.http.SessionManager
import net.mamoe.mirai.api.http.data.*
import net.mamoe.mirai.api.http.data.common.MessageChainDTO
import net.mamoe.mirai.api.http.data.common.VerifyDTO
import net.mamoe.mirai.api.http.data.common.toDTO
import net.mamoe.mirai.api.http.data.common.toMessageChain
import net.mamoe.mirai.api.http.util.toJson
import net.mamoe.mirai.contact.toList
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.uploadImage
import java.net.URL
fun Application.messageModule() {
routing {
miraiGet("/fetchMessage") {
val count: Int = paramOrNull("count")
val fetch = it.messageQueue.fetch(count)
val ls = Array(fetch.size) { index -> fetch[index].toDTO() }
call.respondJson(ls.toList().toJson())
}
miraiVerify<SendDTO>("/sendFriendMessage") {
it.session.bot.getFriend(it.target).sendMessage(it.messageChain.toMessageChain())
call.respondStateCode(StateCode.Success)
}
miraiVerify<SendDTO>("/sendGroupMessage") {
it.session.bot.getGroup(it.target).sendMessage(it.messageChain.toMessageChain())
call.respondStateCode(StateCode.Success)
}
miraiVerify<SendDTO>("/quoteMessage") {
it.session.messageQueue.quoteCache[it.target]?.quoteReply(it.messageChain.toMessageChain())
?: throw NoSuchElementException()
call.respondStateCode(StateCode.Success)
}
miraiVerify<SendImageDTO>("sendImageMessage") {
val bot = it.session.bot
val contact = when {
it.target != null -> bot[it.target]
it.qq != null -> bot.getFriend(it.qq)
it.group != null -> bot.getGroup(it.group)
else -> throw IllegalParamException("target、qq、group不可全为null")
}
val ls = it.urls.map { url -> contact.uploadImage(URL(url)) }
contact.sendMessage(MessageChain(ls))
call.respondJson(ls.map { image -> image.imageId }.toJson())
}
// TODO: 重构
post("uploadImage") {
val parts = call.receiveMultipart().readAllParts()
val sessionKey = parts.value("sessionKey")
if (!SessionManager.containSession(sessionKey)) throw IllegalSessionException
val session = try {
SessionManager[sessionKey] as AuthedSession
} catch (e: TypeCastException) {
throw NotVerifiedSessionException
}
val type = parts.value("type")
parts.file("img")?.apply {
val image = streamProvider().use {
when (type) {
"group" -> session.bot.groups.toList().random().uploadImage(it)
"friend" -> session.bot.qqs.toList().random().uploadImage(it)
else -> null
}
}
image?.apply {
call.respondText(imageId)
} ?: throw IllegalAccessException("图片上传错误")
} ?: throw IllegalAccessException("未知错误")
}
}
}
@Serializable
private data class SendDTO(
override val sessionKey: String,
val target: Long,
val messageChain: MessageChainDTO
) : VerifyDTO()
@Serializable
private data class SendImageDTO(
override val sessionKey: String,
val target: Long? = null,
val qq: Long? = null,
val group: Long? = null,
val urls: List<String>
) : VerifyDTO()

View File

@ -1,64 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.api.http.util
import kotlinx.serialization.*
import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.SerializersModule
import net.mamoe.mirai.api.http.data.common.*
import net.mamoe.mirai.message.data.MessageSource
// 解析失败时直接返回null由路由判断响应400状态
@UseExperimental(ImplicitReflectionSerializer::class)
inline fun <reified T : Any> String.jsonParseOrNull(
serializer: DeserializationStrategy<T>? = null
): T? = try {
if(serializer == null) MiraiJson.json.parse(this) else Json.parse(this)
} catch (e: Exception) { null }
@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
inline fun <reified T : Any> T.toJson(
serializer: SerializationStrategy<T>? = null
): String = if (serializer == null) MiraiJson.json.stringify(this)
else MiraiJson.json.stringify(serializer, this)
// 序列化列表时stringify需要使用的泛型是T而非List<T>
// 因为使用的stringify的stringify(objs: List<T>)重载
@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class)
inline fun <reified T : Any> List<T>.toJson(
serializer: SerializationStrategy<List<T>>? = null
): String = if (serializer == null) MiraiJson.json.stringify(this)
else MiraiJson.json.stringify(serializer, this)
/**
* Json解析规则需要注册支持的多态的类
*/
object MiraiJson {
val json = Json(context = SerializersModule {
polymorphic(MessagePacketDTO.serializer()) {
GroupMessagePacketDTO::class with GroupMessagePacketDTO.serializer()
FriendMessagePacketDTO::class with FriendMessagePacketDTO.serializer()
UnKnownMessagePacketDTO::class with UnKnownMessagePacketDTO.serializer()
}
polymorphic(MessageDTO.serializer()) {
MessageSourceDTO::class with MessageSourceDTO.serializer()
AtDTO::class with AtDTO.serializer()
AtAllDTO::class with AtAllDTO.serializer()
FaceDTO::class with FaceDTO.serializer()
PlainDTO::class with PlainDTO.serializer()
ImageDTO::class with ImageDTO.serializer()
XmlDTO::class with XmlDTO.serializer()
UnknownMessageDTO::class with UnknownMessageDTO.serializer()
}
})
}

View File

@ -1,52 +0,0 @@
# Mirai Console
#### Mirai Console allows you to run Mirai in command lines/terminal.
#### 你可以终端中或命令行环境下运行在Mirai
<br>
#### More Importantly, Mirai Console support <b>Plugins</b>, tells the bot what to do
#### Mirai Console 支持插件系统, 你可以自己开发或使用公开的插件来逻辑化机器人, 如群管
<br>
#### download 下载
#### how to get/write plugins 如何获取/写插件
<br>
<br>
### how to use(如何使用)
#### how to run Mirai Console
<ul>
<li>download mirai-console.jar</li>
<li>open command line/terminal</li>
<li>create a folder and put mirai-console.jar in</li>
<li>cd that folder</li>
<li>"java -jar mirai-console.jar"</li>
</ul>
<ul>
<li>下载mirai-console.jar</li>
<li>打开终端</li>
<li>在任何地方创建一个文件夹, 并放入mirai-console.jar</li>
<li>在终端中打开该文件夹"cd"</li>
<li>输入"java -jar mirai-console.jar"</li>
</ul>
#### how to add plugins
<ul>
<li>After first time of running mirai console</li>
<li>/plugins/folder will be created next to mirai-console.jar</li>
<li>put plugin(.jar) into /plugins/</li>
<li>restart mirai console</li>
<li>checking logger and check if the plugin is loaded successfully</li>
<li>if the plugin has it own Config file, it normally appears in /plugins/{pluginName}/</li>
</ul>
<ul>
<li>在首次运行mirai console后</li>
<li>mirai-console.jar 的同级会出现/plugins/文件夹</li>
<li>将插件(.jar)放入/plugins/文件夹</li>
<li>重启mirai console</li>
<li>在开启后检查日志, 是否成功加载</li>
<li>如该插件有配置文件, 配置文件一般会创建在/plugins/插件名字/ 文件夹下</li>
</ul>

View File

@ -1,47 +0,0 @@
plugins {
id("kotlinx-serialization")
id("kotlin")
id("java")
}
apply(plugin = "com.github.johnrengelman.shadow")
apply(plugin = "java-library")
tasks.withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
manifest {
attributes["Main-Class"] = "net.mamoe.mirai.MiraiConsoleLoader"
}
}
val kotlinVersion: String by rootProject.ext
val atomicFuVersion: String by rootProject.ext
val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext
fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version"
fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
dependencies {
api(project(":mirai-core"))
api(project(":mirai-core-qqandroid"))
api(project(":mirai-api-http"))
runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main"))
runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main"))
api(kotlin("serialization"))
api(group = "com.alibaba", name = "fastjson", version = "1.2.62")
api(group = "org.yaml", name = "snakeyaml", version = "1.25")
api(group = "com.moandjiezana.toml", name = "toml4j", version = "0.7.2")
api(group = "com.googlecode.lanterna", name = "lanterna", version = "3.0.2")
// classpath is not set correctly by IDE
}

View File

@ -1,130 +0,0 @@
package net.mamoe.mirai
/*
* 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
*/
import net.mamoe.mirai.plugins.PluginManager
object CommandManager {
private val registeredCommand: MutableMap<String, ICommand> = mutableMapOf()
fun getCommands(): Collection<ICommand> {
return registeredCommand.values
}
fun register(command: ICommand) {
val allNames = mutableListOf(command.name).also { it.addAll(command.alias) }
allNames.forEach {
if (registeredCommand.containsKey(it)) {
error("net.mamoe.mirai.Command Name(or Alias) $it is already registered, consider if same function plugin was installed")
}
}
allNames.forEach {
registeredCommand[it] = command
}
}
fun unregister(command: ICommand) {
val allNames = mutableListOf<String>(command.name).also { it.addAll(command.alias) }
allNames.forEach {
registeredCommand.remove(it)
}
}
fun unregister(commandName: String) {
registeredCommand.remove(commandName)
}
fun runCommand(fullCommand: String): Boolean {
val blocks = fullCommand.split(" ")
val commandHead = blocks[0].replace("/", "")
if (!registeredCommand.containsKey(commandHead)) {
return false
}
val args = blocks.subList(1, blocks.size)
registeredCommand[commandHead]?.run {
if (onCommand(
blocks.subList(1, blocks.size)
)
) {
PluginManager.onCommand(this, args)
}
}
return true
}
}
interface ICommand {
val name: String
val alias: List<String>
val description: String
fun onCommand(args: List<String>): Boolean
fun register()
}
abstract class Command(
override val name: String,
override val alias: List<String> = listOf(),
override val description: String = ""
) : ICommand {
/**
* 最高优先级监听器
* 如果return [false] 这次指令不会被[PluginBase]的全局onCommand监听器监听
* */
open override fun onCommand(args: List<String>): Boolean {
return true
}
override fun register() {
CommandManager.register(this)
}
}
class AnonymousCommand internal constructor(
override val name: String,
override val alias: List<String>,
override val description: String,
val onCommand: ICommand.(args: List<String>) -> Boolean
) : ICommand {
override fun onCommand(args: List<String>): Boolean {
return onCommand.invoke(this, args)
}
override fun register() {
CommandManager.register(this)
}
}
class CommandBuilder internal constructor() {
var name: String? = null
var alias: List<String>? = null
var description: String = ""
var onCommand: (ICommand.(args: List<String>) -> Boolean)? = null
fun onCommand(commandProcess: ICommand.(args: List<String>) -> Boolean) {
onCommand = commandProcess
}
fun register(): ICommand {
if (name == null || onCommand == null) {
error("net.mamoe.mirai.CommandBuilder not complete")
}
if (alias == null) {
alias = listOf()
}
return AnonymousCommand(name!!, alias!!, description, onCommand!!).also { it.register() }
}
}
fun buildCommand(builder: CommandBuilder.() -> Unit): ICommand {
return CommandBuilder().apply(builder).register()
}

View File

@ -1,295 +0,0 @@
package net.mamoe.mirai
/*
* 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
*/
import kotlinx.coroutines.runBlocking
import net.mamoe.mirai.plugins.PluginManager
import net.mamoe.mirai.plugins.loadAsConfig
import net.mamoe.mirai.plugins.withDefaultWrite
import net.mamoe.mirai.plugins.withDefaultWriteSave
import net.mamoe.mirai.api.http.MiraiHttpAPIServer
import net.mamoe.mirai.api.http.generateSessionKey
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.utils.MiraiLogger
import java.io.File
import java.io.PrintStream
import kotlin.concurrent.thread
object MiraiConsole {
val bots
get() = Bot.instances
fun getBotByUIN(uin: Long): Bot? {
bots.forEach {
if (it.get()?.uin == uin) {
return it.get()
}
}
return null
}
val pluginManager: PluginManager
get() = PluginManager
var logger = UIPushLogger(0)
var path: String = System.getProperty("user.dir")
val version = "0.01"
var coreVersion = "0.13"
val build = "Beta"
fun start() {
logger("Mirai-console v$version $build | core version v$coreVersion is still in testing stage, majority feature is available")
logger("Mirai-console v$version $build | 核心版本 v${coreVersion}还处于测试阶段, 大部分功能可用")
logger()
logger("Mirai-console now running under " + System.getProperty("user.dir"))
logger("Mirai-console 正在 " + System.getProperty("user.dir") + "下运行")
logger()
logger("Get news in github: https://github.com/mamoe/mirai")
logger("在Github中获取项目最新进展: https://github.com/mamoe/mirai")
logger("Mirai为开源项目请自觉遵守开源项目协议")
logger("Powered by Mamoe Technologies and contributors")
logger()
runBlocking {
DefaultCommands()
HTTPAPIAdaptar()
pluginManager.loadPlugins()
CommandListener.start()
}
logger("Mirai-console 启动完成")
logger("\"/login qqnumber qqpassword \" to login a bot")
logger("\"/login qq号 qq密码 \" 来登陆一个BOT")
}
fun stop() {
PluginManager.disableAllPlugins()
}
object HTTPAPIAdaptar {
operator fun invoke() {
if (MiraiProperties.HTTP_API_ENABLE) {
if (MiraiProperties.HTTP_API_AUTH_KEY.startsWith("InitKey")) {
logger("请尽快更改初始生成的HTTP API AUTHKEY")
}
logger("正在启动HTTPAPI; 端口=" + MiraiProperties.HTTP_API_PORT)
MiraiHttpAPIServer.start(
MiraiProperties.HTTP_API_PORT,
MiraiProperties.HTTP_API_AUTH_KEY
)
logger("HTTPAPI启动完成; 端口=" + MiraiProperties.HTTP_API_PORT)
}
}
}
/**
* Defaults Commands are recommend to be replaced by plugin provided commands
*/
object DefaultCommands {
operator fun invoke() {
buildCommand {
name = "login"
description = "Mirai-Console default bot login command"
onCommand {
if (it.size < 2) {
logger("\"/login qqnumber qqpassword \" to login a bot")
logger("\"/login qq号 qq密码 \" 来登录一个BOT")
return@onCommand false
}
val qqNumber = it[0].toLong()
val qqPassword = it[1]
logger("login...")
try {
runBlocking {
Bot(qqNumber, qqPassword).alsoLogin()
println("$qqNumber login successes")
}
} catch (e: Exception) {
println("$qqNumber login failed")
}
true
}
}
buildCommand {
name = "status"
description = "Mirai-Console default status command"
onCommand {
when (it.size) {
0 -> {
logger("当前有" + bots.size + "个BOT在线")
}
1 -> {
val bot = it[0]
var find = false
bots.forEach {
if (it.get()?.uin.toString().contains(bot)) {
find = true
logger("" + it.get()?.uin + ": 在线中; 好友数量:" + it.get()?.qqs?.size + "; 群组数量:" + it.get()?.groups?.size)
}
}
if (!find) {
logger("没有找到BOT$bot")
}
}
}
true
}
}
buildCommand {
name = "say"
description = "Mirai-Console default say command"
onCommand {
if (it.size < 2) {
logger("say [好友qq号或者群号] [文本消息] //将默认使用第一个BOT")
logger("say [bot号] [好友qq号或者群号] [文本消息]")
return@onCommand false
}
val bot: Bot? = if (it.size == 2) {
if (bots.size == 0) {
logger("还没有BOT登陆")
return@onCommand false
}
bots[0].get()
} else {
getBotByUIN(it[0].toLong())
}
if (bot == null) {
logger("没有找到BOT")
return@onCommand false
}
val target = it[it.size - 2].toLong()
val message = it[it.size - 1]
try {
val contact = bot[target]
runBlocking {
contact.sendMessage(message)
logger("消息已推送")
}
} catch (e: NoSuchElementException) {
logger("没有找到群或好友 号码为${target}")
return@onCommand false
}
true
}
}
buildCommand {
name = "plugins"
alias = listOf("plugin")
description = "show all plugins"
onCommand {
PluginManager.getAllPluginDescriptions().let {
println("loaded " + it.size + " plugins")
it.forEach {
logger("\t" + it.name + " v" + it.version + " by" + it.author + " " + it.info)
}
true
}
}
}
buildCommand {
name = "command"
alias = listOf("commands", "help", "helps")
description = "show all commands"
onCommand {
CommandManager.getCommands().let {
println("currently have " + it.size + " commands")
it.toSet().forEach {
logger("\t" + it.name + " :" + it.description)
}
}
true
}
}
buildCommand {
name = "about"
description = "About Mirai-Console"
onCommand {
logger("v$version $build is still in testing stage, majority feature is available")
logger("now running under " + System.getProperty("user.dir"))
logger("在Github中获取项目最新进展: https://github.com/mamoe/mirai")
logger("Mirai为开源项目请自觉遵守开源项目协议")
logger("Powered by Mamoe Technologies and contributors")
true
}
}
}
}
object CommandListener {
fun start() {
thread {
processNextCommandLine()
}
}
tailrec fun processNextCommandLine() {
var fullCommand = readLine()
if (fullCommand != null) {
if (!fullCommand.startsWith("/")) {
fullCommand = "/$fullCommand"
}
if (!CommandManager.runCommand(fullCommand)) {
logger("未知指令 $fullCommand")
}
}
processNextCommandLine();
}
}
class UIPushLogger(val identity: Long) {
operator fun invoke(any: Any? = null) {
MiraiConsoleUI.start()
if (any != null) {
MiraiConsoleUI.pushLog(identity, "[Mirai$version $build]: $any")
}
}
}
object MiraiProperties {
var config = File("$path/mirai.properties").loadAsConfig()
var HTTP_API_ENABLE: Boolean by config.withDefaultWrite { true }
var HTTP_API_PORT: Int by config.withDefaultWrite { 8080 }
var HTTP_API_AUTH_KEY: String by config.withDefaultWriteSave {
"InitKey" + generateSessionKey()
}
}
}
class MiraiConsoleLoader {
companion object {
@JvmStatic
fun main(args: Array<String>) {
MiraiConsole.start()
Runtime.getRuntime().addShutdownHook(thread(start = false) {
MiraiConsole.stop()
})
}
}
}

View File

@ -1,319 +0,0 @@
package net.mamoe.mirai
import com.googlecode.lanterna.SGR
import com.googlecode.lanterna.TerminalSize
import com.googlecode.lanterna.TextColor
import com.googlecode.lanterna.graphics.TextGraphics
import com.googlecode.lanterna.input.KeyStroke
import com.googlecode.lanterna.input.KeyType
import com.googlecode.lanterna.terminal.DefaultTerminalFactory
import com.googlecode.lanterna.terminal.Terminal
import com.googlecode.lanterna.terminal.TerminalResizeListener
import com.googlecode.lanterna.terminal.swing.SwingTerminal
import com.googlecode.lanterna.terminal.swing.SwingTerminalFrame
import java.io.OutputStream
import java.io.PrintStream
import java.lang.StringBuilder
import java.util.*
import kotlin.concurrent.thread
object MiraiConsoleUI {
val log = mutableMapOf<Long, LimitLinkedQueue<String>>().also { it[0L] = LimitLinkedQueue(50) }
private val screens = mutableListOf(0L)
private var currentScreenId = 0
fun addBotScreen(uin: Long) {
screens.add(uin)
log[uin] = LimitLinkedQueue()
}
lateinit var terminal: Terminal
lateinit var textGraphics: TextGraphics
var hasStart = false
fun start() {
if (hasStart) {
return
}
hasStart = true
val defaultTerminalFactory = DefaultTerminalFactory()
try {
terminal = defaultTerminalFactory.createTerminal()
terminal.enterPrivateMode()
terminal.clearScreen()
terminal.setCursorVisible(false)
} catch (e: Exception) {
try {
terminal = SwingTerminalFrame("Mirai Console")
terminal.enterPrivateMode()
terminal.clearScreen()
terminal.setCursorVisible(false)
} catch (e: Exception) {
error("can not create terminal")
}
}
textGraphics = terminal.newTextGraphics()
terminal.addResizeListener(TerminalResizeListener { terminal1: Terminal, newSize: TerminalSize ->
terminal.clearScreen()
inited = false
update()
redrawCommand()
})
update()
val charList = listOf(',', '.', '/', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '=', '+', '!', ' ')
thread {
while (true) {
var keyStroke: KeyStroke = terminal.readInput()
when (keyStroke.keyType) {
KeyType.ArrowLeft -> {
currentScreenId = getLeftScreenId()
update()
}
KeyType.ArrowRight -> {
currentScreenId = getRightScreenId()
update()
}
KeyType.Enter -> {
emptyCommand()
}
else -> {
if (keyStroke.character != null) {
if (keyStroke.character.toInt() == 8) {
deleteCommandChar()
}
if (keyStroke.character.isLetterOrDigit() || charList.contains(keyStroke.character)) {
addCommandChar(keyStroke.character)
}
}
}
}
}
}
}
fun getLeftScreenId(): Int {
var newId = currentScreenId - 1
if (newId < 0) {
newId = screens.size - 1
}
return newId
}
fun getRightScreenId(): Int {
var newId = 1 + currentScreenId
if (newId >= screens.size) {
newId = 0
}
return newId
}
fun getScreenName(id: Int): String {
return when (screens[id]) {
0L -> {
"Console Screen"
}
else -> {
"Bot: ${screens[id]}"
}
}
}
var inited = false
fun clearRows(row: Int) {
textGraphics.putString(0, row, " ".repeat(terminal.terminalSize.columns))
}
fun drawFrame(
title: String
) {
val width = terminal.terminalSize.columns
val height = terminal.terminalSize.rows
terminal.setBackgroundColor(TextColor.ANSI.DEFAULT)
if (!inited) {
val mainTitle = "Mirai Console v0.01 Core v0.15"
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.GREEN
textGraphics.putString((width - mainTitle.length) / 2, 1, mainTitle, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(2, 3, "-".repeat(width - 4))
textGraphics.putString(2, 5, "-".repeat(width - 4))
textGraphics.putString(2, height - 4, "-".repeat(width - 4))
textGraphics.putString(2, height - 3, "|>>>")
textGraphics.putString(width - 3, height - 3, "|")
textGraphics.putString(2, height - 2, "-".repeat(width - 4))
inited = true
}
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val leftName = getScreenName(getLeftScreenId())
clearRows(2)
textGraphics.putString((width - title.length) / 2 - "$leftName << ".length, 2, "$leftName << ")
textGraphics.foregroundColor = TextColor.ANSI.WHITE
textGraphics.backgroundColor = TextColor.ANSI.YELLOW
textGraphics.putString((width - title.length) / 2, 2, title, SGR.BOLD)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val rightName = getScreenName(getRightScreenId())
textGraphics.putString((width + title.length) / 2 + 1, 2, ">> $rightName")
}
fun drawMainFrame(
onlineBotCount: Number
) {
drawFrame("Console Screen")
val width = terminal.terminalSize.columns
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
clearRows(4)
textGraphics.putString(2, 4, "|Online Bots: $onlineBotCount")
textGraphics.putString(
width - 2 - "Powered By Mamoe Technologies|".length,
4,
"Powered By Mamoe Technologies|"
)
}
fun drawBotFrame(
qq: Long,
adminCount: Number
) {
drawFrame("Bot: $qq")
val width = terminal.terminalSize.columns
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
clearRows(4)
textGraphics.putString(2, 4, "|Admins: $adminCount")
textGraphics.putString(width - 2 - "Add admins via commands|".length, 4, "Add admins via commands|")
}
fun drawLogs(values: List<String>) {
val width = terminal.terminalSize.columns - 6
val heightMin = 5
var currentHeight = terminal.terminalSize.rows - 5
for (index in heightMin until currentHeight) {
clearRows(index)
}
values.forEach {
if (currentHeight > heightMin) {
var x = it
while (currentHeight > heightMin) {
if (x.isEmpty() || x.isBlank()) break
textGraphics.foregroundColor = TextColor.ANSI.GREEN
textGraphics.backgroundColor = TextColor.ANSI.DEFAULT
val towrite = if (x.length > width) {
x.substring(0, width).also {
x = x.substring(width)
}
} else {
x.also {
x = ""
}
}
textGraphics.putString(3, currentHeight, towrite, SGR.ITALIC)
--currentHeight
}
}
}
textGraphics.putString(3, 9, "AAAAAAAAAAAAAAAAAAAAAAa", SGR.ITALIC)
terminal.flush()
}
fun pushLog(uin: Long, str: String) {
log[uin]!!.push(str)
if (uin == screens[currentScreenId]) {
drawLogs(log[screens[currentScreenId]]!!)
}
}
var commandBuilder = StringBuilder()
fun redrawCommand() {
val height = terminal.terminalSize.rows
val width = terminal.terminalSize.columns
clearRows(height - 3)
textGraphics.foregroundColor = TextColor.ANSI.DEFAULT
textGraphics.putString(2, height - 3, "|>>>")
textGraphics.putString(width - 3, height - 3, "|")
textGraphics.foregroundColor = TextColor.ANSI.BLUE
textGraphics.putString(7, height - 3, commandBuilder.toString())
if (terminal is SwingTerminalFrame) {
terminal.flush()
}
}
fun addCommandChar(
c: Char
) {
if (commandBuilder.isEmpty() && c != '/') {
addCommandChar('/')
}
textGraphics.foregroundColor = TextColor.ANSI.BLUE
val height = terminal.terminalSize.rows
commandBuilder.append(c)
if (terminal is SwingTerminalFrame) {
redrawCommand()
} else {
textGraphics.putString(6 + commandBuilder.length, height - 3, c.toString())
}
}
fun deleteCommandChar() {
if (!commandBuilder.isEmpty()) {
commandBuilder = StringBuilder(commandBuilder.toString().substring(0, commandBuilder.length - 1))
}
val height = terminal.terminalSize.rows
if (terminal is SwingTerminalFrame) {
redrawCommand()
} else {
textGraphics.putString(7 + commandBuilder.length, height - 3, " ")
}
}
fun emptyCommand() {
commandBuilder = StringBuilder()
redrawCommand()
if (terminal is SwingTerminal) {
terminal.flush()
}
}
fun update() {
when (screens[currentScreenId]) {
0L -> {
drawMainFrame(screens.size - 1)
}
else -> {
drawBotFrame(screens[currentScreenId], 0)
}
}
terminal.flush()
}
}
class LimitLinkedQueue<T>(
val limit: Int = 50
) : LinkedList<T>(), List<T> {
override fun push(e: T) {
if (size >= limit) {
pollLast()
}
super.push(e)
}
}

View File

@ -1,406 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.plugins
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.JSONObject
import com.alibaba.fastjson.TypeReference
import com.alibaba.fastjson.parser.Feature
import com.moandjiezana.toml.Toml
import com.moandjiezana.toml.TomlWriter
import kotlinx.serialization.*
import org.yaml.snakeyaml.Yaml
import java.io.File
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KClass
import kotlin.reflect.KProperty
import kotlin.reflect.full.isSubclassOf
/**
* TODO: support all config types
* only JSON is now supported
*
*/
interface Config {
fun getConfigSection(key: String): ConfigSection
fun getString(key: String): String
fun getInt(key: String): Int
fun getFloat(key: String): Float
fun getDouble(key: String): Double
fun getLong(key: String): Long
fun getBoolean(key: String): Boolean
fun getList(key: String): List<*>
fun getStringList(key: String): List<String>
fun getIntList(key: String): List<Int>
fun getFloatList(key: String): List<Float>
fun getDoubleList(key: String): List<Double>
fun getLongList(key: String): List<Long>
operator fun set(key: String, value: Any)
operator fun get(key: String): Any?
fun exist(key: String): Boolean
fun setIfAbsent(key: String, value: Any)
fun asMap(): Map<String, Any>
fun save()
companion object {
fun load(fileName: String): Config {
return load(File(fileName.replace("//", "/")))
}
fun load(file: File): Config {
if (!file.exists()) {
file.createNewFile()
}
return when (file.extension.toLowerCase()) {
"json" -> JsonConfig(file)
"yml" -> YamlConfig(file)
"yaml" -> YamlConfig(file)
"mirai" -> YamlConfig(file)
"ini" -> TomlConfig(file)
"toml" -> TomlConfig(file)
"properties" -> TomlConfig(file)
"property" -> TomlConfig(file)
"data" -> TomlConfig(file)
else -> error("Unsupported file config type ${file.extension.toLowerCase()}")
}
}
}
}
fun File.loadAsConfig(): Config {
return Config.load(this)
}
/* 最简单的代理 */
inline operator fun <reified T : Any> Config.getValue(thisRef: Any?, property: KProperty<*>): T {
return smartCast(property)
}
inline operator fun <reified T : Any> Config.setValue(thisRef: Any?, property: KProperty<*>, value: T) {
this[property.name] = value
}
/* 带有默认值的代理 */
inline fun <reified T : Any> Config.withDefault(
noinline defaultValue: () -> T
): ReadWriteProperty<Any, T> {
val default by lazy { defaultValue.invoke() }
return object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (this@withDefault.exist(property.name)) {//unsafe
return this@withDefault.smartCast(property)
}
return default
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
this@withDefault[property.name] = value
}
}
}
/* 带有默认值且如果为空会写入的代理 */
inline fun <reified T : Any> Config.withDefaultWrite(
noinline defaultValue: () -> T
): WithDefaultWriteLoader<T> {
return WithDefaultWriteLoader(T::class, this, defaultValue, false)
}
/* 带有默认值且如果为空会写入保存的代理 */
inline fun <reified T : Any> Config.withDefaultWriteSave(
noinline defaultValue: () -> T
): WithDefaultWriteLoader<T> {
return WithDefaultWriteLoader(T::class, this, defaultValue, true)
}
class WithDefaultWriteLoader<T : Any>(
private val _class: KClass<T>,
private val config: Config,
private val defaultValue: () -> T,
private val save: Boolean
) {
operator fun provideDelegate(
thisRef: Any,
prop: KProperty<*>
): ReadWriteProperty<Any, T> {
val defaultValue by lazy { defaultValue.invoke() }
config.setIfAbsent(prop.name, defaultValue)
if (save) {
config.save()
}
return object : ReadWriteProperty<Any, T> {
override fun getValue(thisRef: Any, property: KProperty<*>): T {
if (config.exist(property.name)) {//unsafe
return config._smartCast(property.name, _class)
}
return defaultValue
}
override fun setValue(thisRef: Any, property: KProperty<*>, value: T) {
config[property.name] = value
}
}
}
}
inline fun <reified T : Any> Config.smartCast(property: KProperty<*>): T {
return _smartCast(property.name, T::class)
}
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
fun <T : Any> Config._smartCast(propertyName: String, _class: KClass<T>): T {
return when (_class) {
String::class -> this.getString(propertyName)
Int::class -> this.getInt(propertyName)
Float::class -> this.getFloat(propertyName)
Double::class -> this.getDouble(propertyName)
Long::class -> this.getLong(propertyName)
Boolean::class -> this.getBoolean(propertyName)
else -> when {
_class.isSubclassOf(ConfigSection::class) -> this.getConfigSection(propertyName)
_class == List::class || _class == MutableList::class -> {
val list = this.getList(propertyName)
return if (list.isEmpty()) {
list
} else {
when (list[0]!!::class) {
String::class -> getStringList(propertyName)
Int::class -> getIntList(propertyName)
Float::class -> getFloatList(propertyName)
Double::class -> getDoubleList(propertyName)
Long::class -> getLongList(propertyName)
else -> {
error("unsupported type")
}
}
} as T
}
else -> {
error("unsupported type")
}
}
} as T
}
interface ConfigSection : Config, MutableMap<String, Any> {
override fun getConfigSection(key: String): ConfigSection {
return (get(key) ?: error("ConfigSection does not contain $key ")) as ConfigSection
}
override fun getString(key: String): String {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString()
}
override fun getInt(key: String): Int {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toInt()
}
override fun getFloat(key: String): Float {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toFloat()
}
override fun getBoolean(key: String): Boolean {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toBoolean()
}
override fun getDouble(key: String): Double {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toDouble()
}
override fun getLong(key: String): Long {
return (get(key) ?: error("ConfigSection does not contain $key ")).toString().toLong()
}
override fun getList(key: String): List<*> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>)
}
override fun getStringList(key: String): List<String> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString() }
}
override fun getIntList(key: String): List<Int> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toInt() }
}
override fun getFloatList(key: String): List<Float> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toFloat() }
}
override fun getDoubleList(key: String): List<Double> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toDouble() }
}
override fun getLongList(key: String): List<Long> {
return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toLong() }
}
override fun exist(key: String): Boolean {
return get(key) != null
}
override fun setIfAbsent(key: String, value: Any) {
if (!exist(key)) set(key, value)
}
}
@Serializable
open class ConfigSectionImpl() : ConcurrentHashMap<String, Any>(), ConfigSection {
override fun set(key: String, value: Any) {
super.put(key, value)
}
override operator fun get(key: String): Any? {
return super.get(key)
}
override fun exist(key: String): Boolean {
return containsKey(key)
}
override fun asMap(): Map<String, Any> {
return this
}
override fun save() {
}
override fun setIfAbsent(key: String, value: Any) {
this.putIfAbsent(key, value)//atomic
}
}
open class ConfigSectionDelegation(
private val delegation: MutableMap<String, Any>
) : ConfigSection, MutableMap<String, Any> by delegation {
override fun set(key: String, value: Any) {
delegation.put(key, value)
}
override fun asMap(): Map<String, Any> {
return delegation
}
override fun save() {
}
}
interface FileConfig : Config {
fun deserialize(content: String): ConfigSection
fun serialize(config: ConfigSection): String
}
abstract class FileConfigImpl internal constructor(
private val file: File
) : FileConfig, ConfigSection {
private val content by lazy {
deserialize(file.readText())
}
override val size: Int get() = content.size
override val entries: MutableSet<MutableMap.MutableEntry<String, Any>> get() = content.entries
override val keys: MutableSet<String> get() = content.keys
override val values: MutableCollection<Any> get() = content.values
override fun containsKey(key: String): Boolean = content.containsKey(key)
override fun containsValue(value: Any): Boolean = content.containsValue(value)
override fun put(key: String, value: Any): Any? = content.put(key, value)
override fun isEmpty(): Boolean = content.isEmpty()
override fun putAll(from: Map<out String, Any>) = content.putAll(from)
override fun clear() = content.clear()
override fun remove(key: String): Any? = content.remove(key)
override fun save() {
if (!file.exists()) {
file.createNewFile()
}
file.writeText(serialize(content))
}
override fun get(key: String): Any? {
return content[key]
}
override fun set(key: String, value: Any) {
content[key] = value
}
override fun asMap(): Map<String, Any> {
return content.asMap()
}
}
class JsonConfig internal constructor(
file: File
) : FileConfigImpl(file) {
@UnstableDefault
override fun deserialize(content: String): ConfigSection {
if (content.isEmpty() || content.isBlank() || content == "{}") {
return ConfigSectionImpl()
}
return JSON.parseObject<ConfigSectionImpl>(
content,
object : TypeReference<ConfigSectionImpl>() {},
Feature.OrderedField
)
}
@UnstableDefault
override fun serialize(config: ConfigSection): String {
return JSONObject.toJSONString(config)
}
}
class YamlConfig internal constructor(file: File) : FileConfigImpl(file) {
override fun deserialize(content: String): ConfigSection {
if (content.isEmpty() || content.isBlank()) {
return ConfigSectionImpl()
}
return ConfigSectionDelegation(
Collections.synchronizedMap(
Yaml().load<LinkedHashMap<String, Any>>(content) as LinkedHashMap<String, Any>
)
)
}
override fun serialize(config: ConfigSection): String {
return Yaml().dumpAsMap(config)
}
}
class TomlConfig internal constructor(file: File) : FileConfigImpl(file) {
override fun deserialize(content: String): ConfigSection {
if (content.isEmpty() || content.isBlank()) {
return ConfigSectionImpl()
}
return ConfigSectionDelegation(
Collections.synchronizedMap(
Toml().read(content).toMap()
)
)
}
override fun serialize(config: ConfigSection): String {
return TomlWriter().write(config)
}
}

View File

@ -1,319 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.plugins
import net.mamoe.mirai.ICommand
import kotlinx.coroutines.*
import net.mamoe.mirai.utils.DefaultLogger
import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.io.encodeToString
import java.io.File
import java.net.URL
import java.net.URLClassLoader
import java.util.jar.JarFile
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
abstract class PluginBase(coroutineContext: CoroutineContext) : CoroutineScope {
constructor() : this(EmptyCoroutineContext)
private val supervisorJob = SupervisorJob()
final override val coroutineContext: CoroutineContext = coroutineContext + supervisorJob
val dataFolder: File by lazy {
File(PluginManager.pluginsPath + pluginDescription.name).also { it.mkdir() }
}
/**
* 当一个插件被加载时调用
*/
open fun onLoad() {
}
/**
* 当所有插件全部被加载后被调用
*/
open fun onEnable() {
}
/**
* 当插件关闭前被调用
*/
open fun onDisable() {
}
/**
* 当任意指令被使用
*/
open fun onCommand(command: ICommand, args: List<String>) {
}
internal fun enable() {
this.onEnable()
}
fun loadConfig(fileName: String): Config {
return Config.load(File(fileName))
}
@JvmOverloads
internal fun disable(throwable: CancellationException? = null) {
this.coroutineContext[Job]!!.cancelChildren(throwable)
this.onDisable()
}
private lateinit var pluginDescription: PluginDescription
internal fun init(pluginDescription: PluginDescription) {
this.pluginDescription = pluginDescription
this.onLoad()
}
fun getPluginManager() = PluginManager
val logger: MiraiLogger by lazy {
DefaultLogger(pluginDescription.name)
}
}
class PluginDescription(
val name: String,
val author: String,
val basePath: String,
val version: String,
val info: String,
val depends: List<String>,//插件的依赖
internal var loaded: Boolean = false,
internal var noCircularDepend: Boolean = true
) {
override fun toString(): String {
return "name: $name\nauthor: $author\npath: $basePath\nver: $version\ninfo: $info\ndepends: $depends"
}
companion object {
fun readFromContent(content_: String): PluginDescription {
val content = content_.split("\n")
var name = "Plugin"
var author = "Unknown"
var basePath = "net.mamoe.mirai.PluginMain"
var info = "Unknown"
var version = "1.0.0"
val depends = mutableListOf<String>();
content.forEach {
val line = it.trim()
val lowercaseLine = line.toLowerCase()
if (it.contains(":")) {
when {
lowercaseLine.startsWith("name") -> {
name = line.substringAfter(":").trim()
}
lowercaseLine.startsWith("author") -> {
author = line.substringAfter(":").trim()
}
lowercaseLine.startsWith("info") || lowercaseLine.startsWith("information") -> {
info = line.substringAfter(":").trim()
}
lowercaseLine.startsWith("main") || lowercaseLine.startsWith("path") || lowercaseLine.startsWith(
"basepath"
) -> {
basePath = line.substringAfter(":").trim()
}
lowercaseLine.startsWith("version") || lowercaseLine.startsWith("ver") -> {
version = line.substringAfter(":").trim()
}
}
} else if (line.startsWith("-")) {
depends.add(line.substringAfter("-").trim())
}
}
return PluginDescription(name, author, basePath, version, info, depends)
}
}
}
internal class PluginClassLoader(file: File, parent: ClassLoader) : URLClassLoader(arrayOf(file.toURI().toURL()), parent)
object PluginManager {
internal val pluginsPath = System.getProperty("user.dir") + "/plugins/".replace("//", "/").also {
File(it).mkdirs()
}
val logger = DefaultLogger("Mirai Plugin Manager")
//已完成加载的
private val nameToPluginBaseMap: MutableMap<String, PluginBase> = mutableMapOf()
private val pluginDescriptions: MutableMap<String, PluginDescription> = mutableMapOf()
fun onCommand(command: ICommand, args: List<String>) {
nameToPluginBaseMap.values.forEach {
it.onCommand(command, args)
}
}
fun getAllPluginDescriptions(): Collection<PluginDescription> {
return pluginDescriptions.values
}
/**
* 尝试加载全部插件
*/
fun loadPlugins() {
val pluginsFound: MutableMap<String, PluginDescription> = mutableMapOf()
val pluginsLocation: MutableMap<String, File> = mutableMapOf()
logger.info("""开始加载${pluginsPath}下的插件""")
File(pluginsPath).listFiles()?.forEach { file ->
if (file != null && file.extension == "jar") {
val jar = JarFile(file)
val pluginYml =
jar.entries().asSequence().filter { it.name.toLowerCase().contains("plugin.yml") }.firstOrNull()
if (pluginYml == null) {
logger.info("plugin.yml not found in jar " + jar.name + ", it will not be consider as a Plugin")
} else {
val description =
PluginDescription.readFromContent(URL("jar:file:" + file.absoluteFile + "!/" + pluginYml.name).openConnection().inputStream.use {
it.readBytes().encodeToString()
})
pluginsFound[description.name] = description
pluginsLocation[description.name] = file
}
}
}
fun checkNoCircularDepends(
target: PluginDescription,
needDepends: List<String>,
existDepends: MutableList<String>
) {
if (!target.noCircularDepend) {
return
}
existDepends.add(target.name)
if (needDepends.any { existDepends.contains(it) }) {
target.noCircularDepend = false
}
existDepends.addAll(needDepends)
needDepends.forEach {
if (pluginsFound.containsKey(it)) {
checkNoCircularDepends(pluginsFound[it]!!, pluginsFound[it]!!.depends, existDepends)
}
}
}
pluginsFound.values.forEach {
checkNoCircularDepends(it, it.depends, mutableListOf())
}
//load
fun loadPlugin(description: PluginDescription): Boolean {
if (!description.noCircularDepend) {
logger.error("Failed to load plugin " + description.name + " because it has circular dependency")
return false
}
//load depends first
description.depends.forEach { dependent ->
if (!pluginsFound.containsKey(dependent)) {
logger.error("Failed to load plugin " + description.name + " because it need " + dependent + " as dependency")
return false
}
val depend = pluginsFound[dependent]!!
//还没有加载
if (!depend.loaded && !loadPlugin(pluginsFound[dependent]!!)) {
logger.error("Failed to load plugin " + description.name + " because " + dependent + " as dependency failed to load")
return false
}
}
//在这里所有的depends都已经加载了
//real load
logger.info("loading plugin " + description.name)
try {
val pluginClass = try {
PluginClassLoader(
(pluginsLocation[description.name]!!),
this.javaClass.classLoader
)
.loadClass(description.basePath)
} catch (e: ClassNotFoundException) {
logger.info("failed to find Main: " + description.basePath + " checking if it's kotlin's path")
PluginClassLoader(
(pluginsLocation[description.name]!!),
this.javaClass.classLoader
)
.loadClass("${description.basePath}Kt")
}
return try {
val subClass = pluginClass.asSubclass(PluginBase::class.java)
val plugin: PluginBase = subClass.getDeclaredConstructor().newInstance()
description.loaded = true
logger.info("successfully loaded plugin " + description.name + " version " + description.version + " by " + description.author)
logger.info(description.info)
nameToPluginBaseMap[description.name] = plugin
pluginDescriptions[description.name] = description
plugin.init(description)
true
} catch (e: ClassCastException) {
logger.error("failed to load plugin " + description.name + " , Main class does not extends PluginBase ")
false
}
} catch (e: ClassNotFoundException) {
e.printStackTrace()
logger.error("failed to load plugin " + description.name + " , Main class not found under " + description.basePath)
return false
}
}
pluginsFound.values.forEach {
loadPlugin(it)
}
nameToPluginBaseMap.values.forEach {
it.enable()
}
logger.info("""加载了${nameToPluginBaseMap.size}个插件""")
}
@JvmOverloads
fun disableAllPlugins(throwable: CancellationException? = null) {
nameToPluginBaseMap.values.forEach {
it.disable(throwable)
}
}
}

View File

@ -1,8 +1,3 @@
# mirai-core-qqandroid # mirai-core-qqandroid
Protocol support for QQ for Android for Mirai. Protocol support for QQ for Android for Mirai.
Not yet available to work.
## Protocol Structure
See [README.md](src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/README.md)

View File

@ -5,10 +5,10 @@ plugins {
id("kotlinx-atomicfu") id("kotlinx-atomicfu")
id("kotlinx-serialization") id("kotlinx-serialization")
`maven-publish` `maven-publish`
id("com.jfrog.bintray") version "1.8.4-jetbrains-3" // DO NOT CHANGE THIS VERSION UNLESS YOU WANT TO WASTE YOUR TIME id("com.jfrog.bintray") version "1.8.4-jetbrains-3"
} }
apply(from = rootProject.file("gradle/publish.gradle")) 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
@ -16,7 +16,7 @@ val coroutinesVersion: String by rootProject.ext
val kotlinXIoVersion: String by rootProject.ext val kotlinXIoVersion: String by rootProject.ext
val coroutinesIoVersion: String by rootProject.ext val coroutinesIoVersion: String by rootProject.ext
val klockVersion: String by rootProject.ext
val ktorVersion: String by rootProject.ext val ktorVersion: String by rootProject.ext
val serializationVersion: String by rootProject.ext val serializationVersion: String by rootProject.ext
@ -27,10 +27,12 @@ fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
description = "QQ protocol library" description = "QQ protocol library"
version = rootProject.ext.get("mirai_version")!!.toString()
val isAndroidSDKAvailable: Boolean by project val isAndroidSDKAvailable: Boolean by project
val miraiVersion: String by project
version = miraiVersion
kotlin { kotlin {
if (isAndroidSDKAvailable) { if (isAndroidSDKAvailable) {
apply(from = rootProject.file("gradle/android.gradle")) apply(from = rootProject.file("gradle/android.gradle"))
@ -75,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 {
@ -88,6 +91,7 @@ kotlin {
if (isAndroidSDKAvailable) { if (isAndroidSDKAvailable) {
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
api(kotlinx("serialization-protobuf", serializationVersion))
} }
} }
@ -105,6 +109,7 @@ kotlin {
dependencies { dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
api(kotlinx("serialization-runtime", serializationVersion)) api(kotlinx("serialization-runtime", serializationVersion))
//api(kotlinx("serialization-protobuf", serializationVersion))
} }
} }
@ -120,3 +125,9 @@ kotlin {
} }
} }
} }
//
//tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
// kotlinOptions.jvmTarget = "1.8"
//}
apply(from = rootProject.file("gradle/publish.gradle"))

View File

@ -19,9 +19,23 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
/** /**
* QQ for Android * QQ for Android
*/ */
@Suppress("INAPPLICABLE_JVM_NAME")
actual object QQAndroid : BotFactory { actual object QQAndroid : BotFactory {
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
@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)
} }
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
@OptIn(MiraiInternalAPI::class)
@JvmName("newBot")
actual override fun Bot(
context: Context,
qq: Long,
passwordMd5: ByteArray,
configuration: BotConfiguration
): Bot = QQAndroidBot(context, BotAccount(qq, passwordMd5), configuration)
} }

View File

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

View File

@ -11,16 +11,31 @@ package net.mamoe.mirai.qqandroid
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotFactory import net.mamoe.mirai.BotFactory
import net.mamoe.mirai.qqandroid.QQAndroid.Bot
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 kotlin.jvm.JvmName
/** /**
* QQ for Android * QQ for Android
*/ */
@Suppress("INAPPLICABLE_JVM_NAME")
expect object QQAndroid : BotFactory { expect object QQAndroid : BotFactory {
/** /**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例 * 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/ */
@JvmName("newBot")
override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
@JvmName("newBot")
override fun Bot(
context: Context,
qq: Long,
passwordMd5: ByteArray,
configuration: BotConfiguration
): Bot
} }

View File

@ -10,16 +10,21 @@
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import kotlinx.io.core.Closeable
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.*
import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
import net.mamoe.mirai.message.data.CustomFaceFromFile import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.NotOnlineImageFromFile import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.OfflineFriendImage
import net.mamoe.mirai.message.data.OfflineGroupImage
import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup
import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper import net.mamoe.mirai.qqandroid.network.highway.HighwayHelper
import net.mamoe.mirai.qqandroid.network.highway.postImage import net.mamoe.mirai.qqandroid.network.highway.postImage
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo import net.mamoe.mirai.qqandroid.network.protocol.data.jce.StTroopMemberInfo
@ -31,24 +36,11 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive.MessageSvc
import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString import net.mamoe.mirai.qqandroid.utils.toIpV4AddressString
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.toUHexString import net.mamoe.mirai.utils.io.toUHexString
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo
internal abstract class ContactImpl : Contact {
override fun hashCode(): Int {
var result = bot.hashCode()
result = 31 * result + id.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Contact) return false
if (this::class != other::class) return false
return this.id == other.id && this.bot == other.bot
}
}
internal inline class FriendInfoImpl( internal inline class FriendInfoImpl(
private val jceFriendInfo: JceFriendInfo private val jceFriendInfo: JceFriendInfo
) : FriendInfo { ) : FriendInfo {
@ -61,28 +53,33 @@ internal class QQImpl(
override val coroutineContext: CoroutineContext, override val coroutineContext: CoroutineContext,
override val id: Long, override val id: Long,
private val friendInfo: FriendInfo private val friendInfo: FriendInfo
) : ContactImpl(), QQ { ) : QQ() {
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val nick: String override val nick: String
get() = friendInfo.nick get() = friendInfo.nick
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> {
val event = FriendMessageSendEvent(this, message).broadcast() val event = FriendMessageSendEvent(this, message).broadcast()
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent") throw EventCancelledException("cancelled by FriendMessageSendEvent")
} }
lateinit var source: MessageSource
bot.network.run { bot.network.run {
check( check(
MessageSvc.PbSendMsg.ToFriend( MessageSvc.PbSendMsg.ToFriend(
bot.client, bot.client,
id, id,
event.message event.message
).sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS ) {
source = it
}.sendAndExpect<MessageSvc.PbSendMsg.Response>() is MessageSvc.PbSendMsg.Response.SUCCESS
) { "send message failed" } ) { "send message failed" }
} }
return MessageReceipt(source, this, null)
} }
override suspend fun uploadImage(image: ExternalImage): Image = try { @OptIn(MiraiInternalAPI::class)
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")
} }
@ -102,8 +99,9 @@ internal class QQImpl(
) )
).sendAndExpect<LongConn.OffPicUp.Response>() ).sendAndExpect<LongConn.OffPicUp.Response>()
@Suppress("UNCHECKED_CAST") // bug
return when (response) { return when (response) {
is LongConn.OffPicUp.Response.FileExists -> NotOnlineImageFromFile( is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(
filepath = response.resourceId, filepath = response.resourceId,
md5 = response.imageInfo.fileMd5, md5 = response.imageInfo.fileMd5,
fileLength = response.imageInfo.fileSize.toInt(), fileLength = response.imageInfo.fileSize.toInt(),
@ -114,19 +112,27 @@ 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("0x6ff0070", bot.uin, null, imageInput = image.input, inputSize = image.inputSize, uKeyHex = response.uKey.toUHexString("")) MiraiPlatformUtils.Http.postImage(
"0x6ff0070",
bot.uin,
null,
imageInput = image.input,
inputSize = image.inputSize,
uKeyHex = response.uKey.toUHexString("")
)
//HighwayHelper.uploadImage( //HighwayHelper.uploadImage(
// client = bot.client, // client = bot.client,
// serverIp = response.serverIp[0].toIpV4AddressString(), // serverIp = response.serverIp[0].toIpV4AddressString(),
// serverPort = response.serverPort[0], // serverPort = response.serverPort[0],
// imageInput = image.input, // imageInput = image.input,
// inputSize = image.inputSize.toInt(), // inputSize = image.inputSize.toInt(),
// md5 = image.md5, // fileMd5 = image.md5,
// uKey = response.uKey, // uKey = response.uKey,
// commandId = 1 // commandId = 1
//) //)
// 为什么不能 ??
return NotOnlineImageFromFile( return OfflineFriendImage(
filepath = response.resourceId, filepath = response.resourceId,
md5 = image.md5, md5 = image.md5,
fileLength = image.inputSize.toInt(), fileLength = image.inputSize.toInt(),
@ -144,7 +150,22 @@ internal class QQImpl(
} }
} }
} finally { } finally {
image.input.close() (image.input as? Closeable)?.close()
(image.input as? Closeable)?.close()
}
override fun hashCode(): Int {
var result = bot.hashCode()
result = 31 * result + id.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
@Suppress("DuplicatedCode")
if (this === other) return true
if (other !is Contact) return false
if (this::class != other::class) return false
return this.id == other.id && this.bot == other.bot
} }
@MiraiExperimentalAPI @MiraiExperimentalAPI
@ -162,29 +183,54 @@ internal class QQImpl(
TODO("not implemented") TODO("not implemented")
} }
override fun equals(other: Any?): Boolean { override fun toString(): String = "QQ($id)"
if (this === other) return true
return other is QQ && other.id == this.id
}
override fun hashCode(): Int = super.hashCode()
} }
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
internal class MemberImpl( internal class MemberImpl(
qq: QQImpl, val qq: QQImpl, // 不要 WeakRef
group: GroupImpl, group: GroupImpl,
override val coroutineContext: CoroutineContext, override val coroutineContext: CoroutineContext,
memberInfo: MemberInfo memberInfo: MemberInfo
) : ContactImpl(), Member, QQ by qq { ) : Member() {
override val group: GroupImpl by group.unsafeWeakRef() override val group: GroupImpl by group.unsafeWeakRef()
val qq: QQImpl by qq.unsafeWeakRef()
// region QQ delegate
override val id: Long = qq.id
override val nick: String = qq.nick
@MiraiExperimentalAPI
override suspend fun queryProfile(): Profile = qq.queryProfile()
@MiraiExperimentalAPI
override suspend fun queryPreviousNameList(): PreviousNameList = qq.queryPreviousNameList()
@MiraiExperimentalAPI
override suspend fun queryRemark(): FriendNameRemark = qq.queryRemark()
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> = qq.sendMessage(message)
override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage = qq.uploadImage(image)
// endregion
override var permission: MemberPermission = memberInfo.permission override var permission: MemberPermission = memberInfo.permission
@Suppress("PropertyName")
internal var _nameCard: String = memberInfo.nameCard internal var _nameCard: String = memberInfo.nameCard
@Suppress("PropertyName")
internal var _specialTitle: String = memberInfo.specialTitle internal var _specialTitle: String = memberInfo.specialTitle
@Suppress("PropertyName")
var _muteTimestamp: Int = memberInfo.muteTimestamp
override val muteTimeRemaining: Int =
if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) {
0
} else {
_muteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
}
override var nameCard: String override var nameCard: String
get() = _nameCard get() = _nameCard
set(newValue) { set(newValue) {
@ -220,7 +266,7 @@ internal class MemberImpl(
newValue newValue
).sendWithoutExpect() ).sendWithoutExpect()
} }
MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl).broadcast() MemberSpecialTitleChangeEvent(oldValue, newValue, this@MemberImpl, null).broadcast()
} }
} }
} }
@ -279,62 +325,102 @@ internal class MemberImpl(
} }
} }
override fun equals(other: Any?): Boolean { override fun hashCode(): Int {
if (this === other) return true var result = bot.hashCode()
return other is Member && other.id == this.id result = 31 * result + id.hashCode()
return result
} }
override fun hashCode(): Int = super.hashCode() override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Contact) return false
if (this::class != other::class) return false
return this.id == other.id && this.bot == other.bot
}
override fun toString(): String {
return "Member($id)"
}
} }
internal class MemberInfoImpl( internal class MemberInfoImpl(
private val jceInfo: StTroopMemberInfo, jceInfo: StTroopMemberInfo,
private val groupOwnerId: Long groupOwnerId: Long
) : MemberInfo { ) : MemberInfo {
override val uin: Long get() = jceInfo.memberUin override val uin: Long = jceInfo.memberUin
override val nameCard: String get() = jceInfo.sName ?: "" override val nameCard: String = jceInfo.sName ?: ""
override val nick: String get() = jceInfo.nick override val nick: String = jceInfo.nick
override val permission: MemberPermission override val permission: MemberPermission = when {
get() = when {
jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER
jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER else -> MemberPermission.MEMBER
} }
override val specialTitle: String get() = jceInfo.sSpecialTitle ?: "" override val specialTitle: String = jceInfo.sSpecialTitle ?: ""
override val muteTimestamp: Int = jceInfo.dwShutupTimestap?.toInt() ?: 0
}
@OptIn(ExperimentalContracts::class)
internal fun GroupImpl.Companion.checkIsInstance(expression: Boolean) {
contract {
returns() implies expression
}
check(expression) { "group is not an instanceof GroupImpl!! DO NOT interlace two or more protocol implementations!!" }
} }
/**
* 对GroupImpl
* 中name/announcement的更改会直接向服务器异步汇报
*/
@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>
) : ContactImpl(), Group { ) : Group() {
@Suppress("\"RemoveEmptyClassBody\"") // things will go wrong after removal, don't try
companion object {
}
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
val uin: Long = groupInfo.uin val uin: Long = groupInfo.uin
override lateinit var owner: Member override lateinit var owner: Member
@UseExperimental(MiraiExperimentalAPI::class) @OptIn(MiraiExperimentalAPI::class)
override val botAsMember: Member by lazy {
Member(object : MemberInfo {
override val nameCard: String
get() = bot.nick // TODO: 2020/2/21 机器人在群内的昵称获取
override val permission: MemberPermission
get() = botPermission
override val specialTitle: String
get() = "" // TODO: 2020/2/21 获取机器人在群里的头衔
override val muteTimestamp: Int
get() = botMuteRemaining
override val uin: Long
get() = bot.uin
override val nick: String
get() = bot.nick
})
}
@OptIn(MiraiExperimentalAPI::class)
override lateinit var botPermission: MemberPermission override lateinit var botPermission: MemberPermission
var _botMuteRemaining: Int = groupInfo.botMuteRemaining var _botMuteTimestamp: Int = groupInfo.botMuteRemaining
override val botMuteRemaining: Int = override val botMuteRemaining: Int =
if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) { if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) {
0 0
} else { } else {
_botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt() _botMuteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
} }
override val members: ContactList<Member> = ContactList(members.mapNotNull { override val members: ContactList<Member> = ContactList(members.mapNotNull {
if (it.uin == bot.uin) { if (it.uin == bot.uin) {
botPermission = it.permission botPermission = it.permission
if (it.permission == MemberPermission.OWNER) {
owner = botAsMember
}
null null
} else Member(it).also { member -> } else Member(it).also { member ->
if (member.permission == MemberPermission.OWNER) { if (member.permission == MemberPermission.OWNER) {
@ -344,11 +430,11 @@ internal class GroupImpl(
}.toLockFreeLinkedList()) }.toLockFreeLinkedList())
internal var _name: String = groupInfo.name internal var _name: String = groupInfo.name
internal var _announcement: String = groupInfo.memo private var _announcement: String = groupInfo.memo
internal var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite private var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite
internal var _confessTalk: Boolean = groupInfo.confessTalk internal var _confessTalk: Boolean = groupInfo.confessTalk
internal var _muteAll: Boolean = groupInfo.muteAll internal var _muteAll: Boolean = groupInfo.muteAll
internal var _autoApprove: Boolean = groupInfo.autoApprove private var _autoApprove: Boolean = groupInfo.autoApprove
internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat
override var name: String override var name: String
@ -414,12 +500,14 @@ internal class GroupImpl(
override var isAutoApproveEnabled: Boolean override var isAutoApproveEnabled: Boolean
get() = _autoApprove get() = _autoApprove
@Suppress("UNUSED_PARAMETER")
set(newValue) { set(newValue) {
TODO() TODO()
} }
override var isAnonymousChatEnabled: Boolean override var isAnonymousChatEnabled: Boolean
get() = _anonymousChat get() = _anonymousChat
@Suppress("UNUSED_PARAMETER")
set(newValue) { set(newValue) {
TODO() TODO()
} }
@ -465,15 +553,17 @@ internal class GroupImpl(
} }
} }
@MiraiExperimentalAPI
override suspend fun quit(): Boolean { override suspend fun quit(): Boolean {
check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" } check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" }
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(
bot.QQ(memberInfo) as QQImpl, @OptIn(LowLevelAPI::class)
bot._lowLevelNewQQ(memberInfo) as QQImpl,
this, this,
this.coroutineContext, this.coroutineContext,
memberInfo memberInfo
@ -482,7 +572,8 @@ internal class GroupImpl(
override operator fun get(id: Long): Member { override operator fun get(id: Long): Member {
return members.delegate.filteringGetOrNull { it.id == id } ?: throw NoSuchElementException("member $id not found in group $uin") return members.delegate.filteringGetOrNull { it.id == id }
?: throw NoSuchElementException("member $id not found in group $uin")
} }
override fun contains(id: Long): Boolean { override fun contains(id: Long): Boolean {
@ -493,25 +584,31 @@ internal class GroupImpl(
return members.delegate.filteringGetOrNull { it.id == id } return members.delegate.filteringGetOrNull { it.id == id }
} }
override suspend fun sendMessage(message: MessageChain) { override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group> {
check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" } check(!isBotMuted) { "bot is muted. Remaining seconds=$botMuteRemaining" }
val event = GroupMessageSendEvent(this, message).broadcast() val event = GroupMessageSendEvent(this, message).broadcast()
if (event.isCancelled) { if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent") throw EventCancelledException("cancelled by FriendMessageSendEvent")
} }
lateinit var source: MessageSourceFromSendGroup
bot.network.run { bot.network.run {
val response = MessageSvc.PbSendMsg.ToGroup( val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
bot.client, bot.client,
id, id,
event.message event.message
).sendAndExpect<MessageSvc.PbSendMsg.Response>() ) {
source = it
source.startWaitingSequenceId(this)
}.sendAndExpect()
check( check(
response is MessageSvc.PbSendMsg.Response.SUCCESS response is MessageSvc.PbSendMsg.Response.SUCCESS
) { "send message failed: $response" } ) { "send message failed: $response" }
} }
return MessageReceipt(source, this, botAsMember)
} }
override suspend fun uploadImage(image: ExternalImage): Image = try { override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage = 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")
} }
@ -528,9 +625,11 @@ internal class GroupImpl(
filename = image.filename filename = image.filename
).sendAndExpect() ).sendAndExpect()
@Suppress("UNCHECKED_CAST") // bug
when (response) { when (response) {
is ImgStore.GroupPicUp.Response.Failed -> { is ImgStore.GroupPicUp.Response.Failed -> {
ImageUploadEvent.Failed(this@GroupImpl, image, response.resultCode, response.message).broadcast() ImageUploadEvent.Failed(this@GroupImpl, image, response.resultCode, response.message).broadcast()
if (response.message == "over file size max") throw OverFileSizeMaxException()
error("upload group image failed with reason ${response.message}") error("upload group image failed with reason ${response.message}")
} }
is ImgStore.GroupPicUp.Response.FileExists -> { is ImgStore.GroupPicUp.Response.FileExists -> {
@ -546,22 +645,25 @@ internal class GroupImpl(
// fileId = response.fileId.toInt() // fileId = response.fileId.toInt()
// ) // )
// println("NMSL") // println("NMSL")
return CustomFaceFromFile( return OfflineGroupImage(
md5 = image.md5, md5 = image.md5,
filepath = resourceId filepath = resourceId
).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } ).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() }
} }
is ImgStore.GroupPicUp.Response.RequireUpload -> { is ImgStore.GroupPicUp.Response.RequireUpload -> {
// 每 10KB 等 1 秒
withTimeoutOrNull(image.inputSize * 1000 / 1024 / 10) {
HighwayHelper.uploadImage( HighwayHelper.uploadImage(
client = bot.client, client = bot.client,
serverIp = response.uploadIpList.first().toIpV4AddressString(), serverIp = response.uploadIpList.first().toIpV4AddressString(),
serverPort = response.uploadPortList.first(), serverPort = response.uploadPortList.first(),
imageInput = image.input, imageInput = image.input,
inputSize = image.inputSize.toInt(), inputSize = image.inputSize.toInt(),
md5 = image.md5, fileMd5 = image.md5,
uKey = response.uKey, uKey = response.uKey,
commandId = 2 commandId = 2
) )
} ?: error("timeout uploading image: ${image.filename}")
val resourceId = image.calculateImageResourceId() val resourceId = image.calculateImageResourceId()
// return NotOnlineImageFromFile( // return NotOnlineImageFromFile(
// resourceId = resourceId, // resourceId = resourceId,
@ -573,7 +675,7 @@ internal class GroupImpl(
// imageType = image.imageType, // imageType = image.imageType,
// fileId = response.fileId.toInt() // fileId = response.fileId.toInt()
// ) // )
return CustomFaceFromFile( return OfflineGroupImage(
md5 = image.md5, md5 = image.md5,
filepath = resourceId filepath = resourceId
).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() } ).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() }
@ -597,13 +699,24 @@ internal class GroupImpl(
} }
} }
} finally { } finally {
image.input.close() (image.input as? Closeable)?.close()
}
override fun toString(): String {
return "Group($id)"
}
override fun hashCode(): Int {
var result = bot.hashCode()
result = 31 * result + id.hashCode()
return result
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
@Suppress("DuplicatedCode", "DuplicatedCode")
if (this === other) return true if (this === other) return true
return other is Group && other.id == this.id if (other !is Contact) return false
if (this::class != other::class) return false
return this.id == other.id && this.bot == other.bot
} }
override fun hashCode(): Int = super.hashCode()
} }

View File

@ -9,41 +9,46 @@
package net.mamoe.mirai.qqandroid package net.mamoe.mirai.qqandroid
import kotlinx.io.core.ByteReadPacket import io.ktor.client.request.get
import io.ktor.client.statement.HttpResponse
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.contact.ContactList import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.filteringGetOrNull
import net.mamoe.mirai.data.AddFriendResult import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.event.events.MessageRecallEvent
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.qqandroid.message.OnlineFriendImageImpl
import net.mamoe.mirai.qqandroid.message.OnlineGroupImageImpl
import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler import net.mamoe.mirai.qqandroid.network.QQAndroidBotNetworkHandler
import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.QQAndroidClient
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.GroupInfoImpl
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.PbMessageSvc
import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.qqandroid.network.protocol.packet.chat.TroopManagement
import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList import net.mamoe.mirai.qqandroid.network.protocol.packet.list.FriendList
import net.mamoe.mirai.utils.* 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,
configuration: BotConfiguration configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) { ) : BotImpl<QQAndroidBotNetworkHandler>(context, account, configuration) {
val client: QQAndroidClient = val client: QQAndroidClient =
QQAndroidClient( QQAndroidClient(
context, context,
@ -53,17 +58,32 @@ internal abstract class QQAndroidBotBase constructor(
) )
internal var firstLoginSucceed: Boolean = false internal var firstLoginSucceed: Boolean = false
override val uin: Long get() = client.uin override val uin: Long get() = client.uin
override val qqs: ContactList<QQ> = ContactList(LockFreeLinkedList())
@Deprecated(
"use friends instead",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("this.friends")
)
override val qqs: ContactList<QQ>
get() = friends
override val friends: ContactList<QQ> = ContactList(LockFreeLinkedList())
override val selfQQ: QQ by lazy { override val selfQQ: QQ by lazy {
QQ(object : FriendInfo { @OptIn(LowLevelAPI::class)
_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
}) })
} }
override fun QQ(friendInfo: FriendInfo): QQ { @LowLevelAPI
return QQImpl(this as QQAndroidBot, coroutineContext, friendInfo.uin, friendInfo) override fun _lowLevelNewQQ(friendInfo: FriendInfo): QQ {
return QQImpl(
this as QQAndroidBot,
coroutineContext + CoroutineName("QQ(${friendInfo.uin}"),
friendInfo.uin,
friendInfo
)
} }
override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler { override fun createNetworkHandler(coroutineContext: CoroutineContext): QQAndroidBotNetworkHandler {
@ -74,28 +94,36 @@ internal abstract class QQAndroidBotBase constructor(
// internally visible only // internally visible only
fun getGroupByUin(uin: Long): Group { fun getGroupByUin(uin: Long): Group {
return groups.delegate.filteringGetOrNull { (it as GroupImpl).uin == uin } ?: throw NoSuchElementException("Can not found group with ID=${uin}") return groups.delegate.getOrNull(uin) ?: throw NoSuchElementException("Can not found group with ID=${uin}")
} }
fun getGroupByUinOrNull(uin: Long): Group? { fun getGroupByUinOrNull(uin: Long): Group? {
return groups.delegate.filteringGetOrNull { (it as GroupImpl).uin == uin } return groups.delegate.getOrNull(uin)
} }
override suspend fun queryGroupList(): Sequence<Long> { @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupList(): Sequence<Long> {
return network.run { return network.run {
FriendList.GetTroopListSimplify(bot.client) FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2) .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode } }.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode }
} }
override suspend fun queryGroupInfo(id: Long): GroupInfo = network.run { @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo = network.run {
TroopManagement.GetGroupInfo( TroopManagement.GetGroupInfo(
client = bot.client, client = bot.client,
groupCode = id groupCode = groupCode
).sendAndExpect<GroupInfoImpl>() ).sendAndExpect<GroupInfoImpl>(retry = 2)
} }
override suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo> = network.run { @OptIn(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupMemberList(
groupUin: Long,
groupCode: Long,
ownerId: Long
): Sequence<MemberInfo> =
network.run {
var nextUin = 0L var nextUin = 0L
var sequence = sequenceOf<MemberInfoImpl>() var sequence = sequenceOf<MemberInfoImpl>()
while (true) { while (true) {
@ -116,24 +144,88 @@ internal abstract class QQAndroidBotBase constructor(
return sequence return sequence
} }
override fun onEvent(event: BotEvent): Boolean {
return firstLoginSucceed
}
override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult { override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult {
TODO("not implemented") TODO("not implemented")
} }
override suspend fun Image.download(): ByteReadPacket { override suspend fun recall(source: MessageSource) {
TODO("not implemented") if (source.senderId != uin && source.groupId != 0L) {
getGroup(source.groupId).checkBotPermissionOperator()
} }
@Suppress("OverridingDeprecatedMember") // println(source._miraiContentToString())
override suspend fun Image.downloadAsByteArray(): ByteArray { source.ensureSequenceIdAvailable()
TODO("not implemented")
network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response =
if (source.groupId == 0L) {
PbMessageSvc.PbMsgWithDraw.Friend(
bot.client,
source.senderId,
source.sequenceId,
source.messageRandom,
source.time
).sendAndExpect()
} else {
MessageRecallEvent.GroupRecall(
bot,
source.senderId,
source.id,
source.time.toInt(),
null,
getGroup(source.groupId)
).broadcast()
PbMessageSvc.PbMsgWithDraw.Group(
bot.client,
source.groupId,
source.sequenceId,
source.messageRandom
).sendAndExpect()
} }
override suspend fun approveFriendAddRequest(id: Long, remark: String?) { check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.id}: $response" }
TODO("not implemented")
} }
} }
@OptIn(LowLevelAPI::class)
override suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long) {
network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response =
PbMessageSvc.PbMsgWithDraw.Friend(client, friendId, (messageId shr 32).toInt(), messageId.toInt(), time)
.sendAndExpect()
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${messageId}: $response" }
}
}
@OptIn(LowLevelAPI::class)
override suspend fun _lowLevelRecallGroupMessage(groupId: Long, messageId: Long) {
network.run {
val response: PbMessageSvc.PbMsgWithDraw.Response =
PbMessageSvc.PbMsgWithDraw.Group(client, groupId, (messageId shr 32).toInt(), messageId.toInt())
.sendAndExpect()
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${messageId}: $response" }
}
}
override suspend fun queryImageUrl(image: Image): String = when (image) {
is OnlineFriendImageImpl -> image.originUrl
is OnlineGroupImageImpl -> image.originUrl
is OfflineGroupImage -> {
TODO("暂不支持获取离线图片链接")
}
is OfflineFriendImage -> {
TODO("暂不支持获取离线图片链接")
}
else -> error("unsupported image class: ${image::class.simpleName}")
}
override suspend fun openChannel(image: Image): ByteReadChannel {
return MiraiPlatformUtils.Http.get<HttpResponse>(queryImageUrl(image)).content.toKotlinByteReadChannel()
}
}
@Suppress("DEPRECATION")
@OptIn(MiraiInternalAPI::class)
internal expect fun io.ktor.utils.io.ByteReadChannel.toKotlinByteReadChannel(): ByteReadChannel

View File

@ -0,0 +1,14 @@
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
}

View File

@ -12,16 +12,39 @@ package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.charsets.Charset import kotlinx.io.charsets.Charset
import kotlinx.io.core.* import kotlinx.io.core.*
import kotlinx.serialization.* import kotlinx.serialization.*
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.MapEntrySerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.internal.* import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.EmptyModule import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule import kotlinx.serialization.modules.SerialModule
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.ProtoBuf import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.BYTE
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.DOUBLE
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.FLOAT
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.INT
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.JCE_MAX_STRING_LENGTH
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.LIST
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.LONG
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.MAP
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.SHORT
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.SIMPLE_LIST
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.STRING1
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.STRING4
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.STRUCT_BEGIN
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.STRUCT_END
import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce.Companion.ZERO_TYPE
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceHead
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.readString
import net.mamoe.mirai.utils.io.toReadPacket import net.mamoe.mirai.utils.io.toReadPacket
@PublishedApi @PublishedApi
internal val CharsetGBK = Charset.forName("GBK") internal val CharsetGBK = Charset.forName("GBK")
@PublishedApi @PublishedApi
internal val CharsetUTF8 = Charset.forName("UTF8") internal val CharsetUTF8 = Charset.forName("UTF8")
@ -30,12 +53,15 @@ enum class JceCharset(val kotlinCharset: Charset) {
UTF8(Charset.forName("UTF8")) UTF8(Charset.forName("UTF8"))
} }
internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<SerialId>(index)?.id internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<JceId>(index)?.id
/** /**
* Jce 数据结构序列化和反序列化工具, 能将 kotlinx.serialization 通用的注解标记格式的 `class` 序列化为 [ByteArray] * Jce 数据结构序列化和反序列化工具, 能将 kotlinx.serialization 通用的注解标记格式的 `class` 序列化为 [ByteArray]
*/ */
class Jce private constructor(private val charset: JceCharset, context: SerialModule = EmptyModule) : AbstractSerialFormat(context), BinaryFormat { @Suppress("DEPRECATION_ERROR")
@OptIn(InternalSerializationApi::class)
class JceOld private constructor(private val charset: JceCharset, override val context: SerialModule = EmptyModule) :
SerialFormat, BinaryFormat {
private inner class ListWriter( private inner class ListWriter(
private val count: Int, private val count: Int,
@ -46,7 +72,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return 0 return 0
} }
override fun endEncode(desc: SerialDescriptor) { override fun endEncode(descriptor: SerialDescriptor) {
parentEncoder.writeHead(LIST, this.tag) parentEncoder.writeHead(LIST, this.tag)
parentEncoder.encodeTaggedInt(0, count) parentEncoder.encodeTaggedInt(0, count)
parentEncoder.output.writePacket(this.output.build()) parentEncoder.output.writePacket(this.output.build())
@ -68,11 +94,18 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
parentEncoder.output.write(this.output.toByteArray()) parentEncoder.output.write(this.output.toByteArray())
}*/ }*/
override fun beginCollection(desc: SerialDescriptor, collectionSize: Int, vararg typeParams: KSerializer<*>): CompositeEncoder { override fun beginCollection(
descriptor: SerialDescriptor,
collectionSize: Int,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder {
return this return this
} }
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder { override fun beginStructure(
descriptor: SerialDescriptor,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder {
return this return this
} }
} }
@ -81,11 +114,11 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
* From: com.qq.taf.jce.JceOutputStream * From: com.qq.taf.jce.JceOutputStream
*/ */
@Suppress("unused", "MemberVisibilityCanBePrivate") @Suppress("unused", "MemberVisibilityCanBePrivate")
@UseExperimental(ExperimentalIoApi::class) @OptIn(ExperimentalIoApi::class)
private open inner class JceEncoder( private open inner class JceEncoder(
internal val output: BytePacketBuilder internal val output: BytePacketBuilder
) : TaggedEncoder<Int>() { ) : TaggedEncoder<Int>() {
override val context get() = this@Jce.context override val context get() = this@JceOld.context
override fun SerialDescriptor.getTag(index: Int): Int { override fun SerialDescriptor.getTag(index: Int): Int {
return getSerialId(this, index) ?: error("cannot find @SerialId") return getSerialId(this, index) ?: error("cannot find @SerialId")
@ -94,28 +127,38 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
/** /**
* 序列化最开始的时候的 * 序列化最开始的时候的
*/ */
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 -> this StructureKind.LIST -> this
StructureKind.MAP -> this StructureKind.MAP -> this
StructureKind.CLASS, UnionKind.OBJECT -> this StructureKind.CLASS, StructureKind.OBJECT -> this
is PolymorphicKind -> this is PolymorphicKind -> this
else -> throw SerializationException("Primitives are not supported at top-level") else -> throw SerializationException("Primitives are not supported at top-level")
} }
@UseExperimental(ImplicitReflectionSerializer::class) @OptIn(ImplicitReflectionSerializer::class)
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING") @Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) { override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
is MapLikeDescriptor -> { serializer.descriptor.kind == StructureKind.MAP -> {
try {
val entries = (value as Map<*, *>).entries val entries = (value as Map<*, *>).entries
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)
this.writeHead(MAP, currentTag) this.writeHead(MAP, currentTag)
this.encodeTaggedInt(0, entries.count()) this.encodeTaggedInt(0, entries.count())
HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries) SetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries)
} catch (e: Exception) {
super.encodeSerializableValue(serializer, value)
} }
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray) }
is PrimitiveArrayDescriptor -> { serializer.descriptor.kind == StructureKind.LIST
&& value is ByteArray -> encodeTaggedByteArray(popTag(), value as ByteArray)
serializer.descriptor.kind == StructureKind.LIST
&& serializer.descriptor.getElementDescriptor(0) is PrimitiveKind -> {
serializer.serialize( serializer.serialize(
ListWriter( ListWriter(
when (value) { when (value) {
@ -133,9 +176,8 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
value value
) )
} }
is ArrayClassDesc -> { serializer.descriptor.kind == StructureKind.LIST && value is Array<*> -> {
val descriptor = serializer.descriptor as ReferenceArraySerializer<Any, Any?> if (serializer.descriptor.getElementDescriptor(0).kind is PrimitiveKind.BYTE) {
if (descriptor.typeParams.isNotEmpty() && descriptor.typeParams[0] is ByteSerializer) {
encodeTaggedByteArray(popTag(), (value as Array<Byte>).toByteArray()) encodeTaggedByteArray(popTag(), (value as Array<Byte>).toByteArray())
} else } else
serializer.serialize( serializer.serialize(
@ -143,7 +185,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
value value
) )
} }
is ListLikeDescriptor -> { serializer.descriptor.kind == StructureKind.LIST -> {
serializer.serialize( serializer.serialize(
ListWriter((value as Collection<*>).size, popTag(), this), ListWriter((value as Collection<*>).size, popTag(), this),
value value
@ -262,7 +304,8 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
is Double -> encodeTaggedDouble(tag, value) is Double -> encodeTaggedDouble(tag, value)
is Boolean -> encodeTaggedBoolean(tag, value) is Boolean -> encodeTaggedBoolean(tag, value)
is String -> encodeTaggedString(tag, value) is String -> encodeTaggedString(tag, value)
is Unit -> encodeTaggedUnit(tag) is Unit -> {
}
else -> error("unsupported type: ${value.getClassName()}") else -> error("unsupported type: ${value.getClassName()}")
} }
} }
@ -286,7 +329,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
val size: Int, val size: Int,
input: JceInput input: JceInput
) : JceDecoder(input) { ) : JceDecoder(input) {
override fun decodeCollectionSize(desc: SerialDescriptor): Int { override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
return size return size
} }
@ -300,7 +343,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
val size: Int, val size: Int,
input: JceInput input: JceInput
) : JceDecoder(input) { ) : JceDecoder(input) {
override fun decodeCollectionSize(desc: SerialDescriptor): Int { override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
return size return size
} }
@ -312,7 +355,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
private open inner class JceStructReader( private open inner class JceStructReader(
input: JceInput input: JceInput
) : JceDecoder(input) { ) : JceDecoder(input) {
override fun endStructure(desc: SerialDescriptor) { override fun endStructure(descriptor: SerialDescriptor) {
} }
} }
@ -342,22 +385,27 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return input.readInt(tag) return input.readInt(tag)
} }
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
return 0
}
/** /**
* [KSerializer.serialize] * [KSerializer.serialize]
*/ */
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder { override fun beginStructure(descriptor: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeDecoder {
//// println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}") //// println("beginStructure: desc=${desc.getClassName()}, typeParams: ${typeParams.contentToString()}")
when (desc) { when {
// 由于 Byte 的数组有两种方式写入, 需特定读取器 // 由于 Byte 的数组有两种方式写入, 需特定读取器
ByteArraySerializer.descriptor -> { descriptor.kind == StructureKind.LIST
&& descriptor.getElementDescriptor(0).kind == PrimitiveKind.BYTE -> {
// ByteArray, 交给 decodeSerializableValue 进行处理 // ByteArray, 交给 decodeSerializableValue 进行处理
return this return this
} }
is ListLikeDescriptor -> { descriptor.kind == StructureKind.LIST -> {
if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) { // if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) {
// Array<Byte> // // Array<Byte>
return this // 交给 decodeSerializableValue // return this // 交给 decodeSerializableValue
} // }
val tag = currentTagOrNull val tag = currentTagOrNull
@Suppress("SENSELESS_COMPARISON") // 推断 bug @Suppress("SENSELESS_COMPARISON") // 推断 bug
@ -371,12 +419,12 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
MAP -> JceMapReader(input.readInt(0), this.input) MAP -> JceMapReader(input.readInt(0), this.input)
else -> error("type mismatch") else -> error("type mismatch")
} }
} == null && desc.isNullable) { } == null && descriptor.isNullable) {
return NullReader(this.input) return NullReader(this.input)
} }
} }
is MapLikeDescriptor -> { descriptor.kind == StructureKind.MAP -> {
val tag = currentTagOrNull val tag = currentTagOrNull
if (tag != null) { if (tag != null) {
popTag() popTag()
@ -391,7 +439,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return NullReader(this.input) return NullReader(this.input)
} }
return super.beginStructure(desc, *typeParams) return super.beginStructure(descriptor, *typeParams)
} }
override fun decodeTaggedNull(tag: Int): Nothing? { override fun decodeTaggedNull(tag: Int): Nothing? {
@ -410,7 +458,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? { override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
// //
//println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}") println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}")
if (deserializer is NullReader) { if (deserializer is NullReader) {
return null return null
} }
@ -419,13 +467,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return null return null
} }
} }
when (deserializer.descriptor) { when {
ByteArraySerializer.descriptor -> { deserializer.descriptor == ByteArraySerializer().descriptor -> {
val tag = popTag() val tag = popTag()
return if (isTagMissing(tag)) input.readByteArrayOrNull(tag) as? T return if (isTagMissing(tag)) input.readByteArrayOrNull(tag) as? T
else input.readByteArray(tag) as T else input.readByteArray(tag) as T
} }
is ListLikeDescriptor -> { deserializer.descriptor.kind == StructureKind.LIST -> {
if (deserializer is ReferenceArraySerializer<*, *> if (deserializer is ReferenceArraySerializer<*, *>
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams.isNotEmpty() && (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams.isNotEmpty()
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams[0] is ByteSerializer && (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams[0] is ByteSerializer
@ -453,15 +501,17 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} }
error("UNREACHABLE CODE") error("UNREACHABLE CODE")
} }
is MapLikeDescriptor -> { deserializer.descriptor.kind == StructureKind.MAP -> {
val tag = popTag() val tag = popTag()
@Suppress("SENSELESS_COMPARISON") @Suppress("SENSELESS_COMPARISON")
if (input.skipToTagOrNull(tag) { head -> if (input.skipToTagOrNull(tag) { head ->
check(head.type == MAP) { "type mismatch: ${head.type}" } check(head.type == MAP) { "type mismatch: ${head.type}" }
// 将 mapOf(k1 to v1, k2 to v2, ...) 转换为 listOf(k1, v1, k2, v2, ...) 以便于写入. // 将 mapOf(k1 to v1, k2 to v2, ...) 转换为 listOf(k1, v1, k2, v2, ...) 以便于写入.
val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>) val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) val mapEntrySerial =
val setOfEntries = HashSetSerializer(mapEntrySerial).deserialize(JceMapReader(input.readInt(0), input)) MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
val setOfEntries =
SetSerializer(mapEntrySerial).deserialize(JceMapReader(input.readInt(0), input))
return setOfEntries.associateBy({ it.key }, { it.value }) as T return setOfEntries.associateBy({ it.key }, { it.value }) as T
} == null) { } == null) {
if (isTagMissing(tag)) { if (isTagMissing(tag)) {
@ -472,7 +522,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} }
} }
if (deserializer.descriptor.kind == StructureKind.CLASS || deserializer.descriptor.kind == UnionKind.OBJECT) { if (deserializer.descriptor.kind == StructureKind.CLASS || deserializer.descriptor.kind == StructureKind.OBJECT) {
val tag = currentTagOrNull val tag = currentTagOrNull
if (tag != null) { if (tag != null) {
@Suppress("SENSELESS_COMPARISON") // 推断 bug @Suppress("SENSELESS_COMPARISON") // 推断 bug
@ -515,7 +565,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} }
@UseExperimental(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
internal inner class JceInput( internal inner class JceInput(
@PublishedApi @PublishedApi
internal val input: ByteReadPacket, internal val input: ByteReadPacket,
@ -556,22 +606,38 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} }
tag = readUByte().toUInt() tag = readUByte().toUInt()
} }
currentJceHead = JceHead(tag = tag.toInt(), type = type.toByte()) currentJceHead = JceHead(
tag = tag.toInt(),
type = type.toByte()
)
// println("doReadHead: $currentJceHead") // println("doReadHead: $currentJceHead")
return currentJceHead return currentJceHead
} }
fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") fun readBoolean(tag: Int): Boolean =
fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") readBooleanOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByte(tag: Int): Byte =
readByteOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readShort(tag: Int): Short =
readShortOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") fun readLong(tag: Int): Long =
fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") readLongOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readString(tag: Int): String = readStringOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") fun readFloat(tag: Int): Float =
readFloatOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByteArray(tag: Int): ByteArray = readByteArrayOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead") fun readDouble(tag: Int): Double =
readDoubleOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readString(tag: Int): String =
readStringOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByteArray(tag: Int): ByteArray =
readByteArrayOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) { fun readByteArrayOrNull(tag: Int): ByteArray? = skipToTagOrNull(tag) {
when (it.type) { when (it.type) {
@ -667,7 +733,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} while (head.type.toInt() != 11) } while (head.type.toInt() != 11)
} }
@UseExperimental(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
@PublishedApi @PublishedApi
internal fun skipField(type: Byte) = when (type.toInt()) { internal fun skipField(type: Byte) = when (type.toInt()) {
0 -> this.input.discardExact(1) 0 -> this.input.discardExact(1)
@ -704,10 +770,10 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
companion object { companion object {
val UTF8 = Jce(JceCharset.UTF8) val UTF8 = JceOld(JceCharset.UTF8)
val GBK = Jce(JceCharset.GBK) val GBK = JceOld(JceCharset.GBK)
fun byCharSet(c: JceCharset): Jce { fun byCharSet(c: JceCharset): JceOld {
return if (c == JceCharset.UTF8) { return if (c == JceCharset.UTF8) {
UTF8 UTF8
} else { } else {
@ -715,24 +781,9 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} }
} }
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
private fun Any?.getClassName(): String = private fun Any?.getClassName(): String =
(if (this == null) Unit::class else this::class).qualifiedName?.split(".")?.takeLast(2)?.joinToString(".") ?: "<unnamed class>" (if (this == null) Unit::class else this::class).qualifiedName?.split(".")?.takeLast(2)?.joinToString(".")
?: "<unnamed class>"
} }
fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket { fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket {
@ -742,14 +793,18 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return encoder.build() return encoder.build()
} }
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray { override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
return dumpAsPacket(serializer, obj).readBytes() return dumpAsPacket(serializer, value).readBytes()
} }
/** /**
* 注意 close [packet]!! * 注意 close [packet]!!
*/ */
fun <T> load(deserializer: DeserializationStrategy<T>, packet: ByteReadPacket, length: Int = packet.remaining.toInt()): T { fun <T> load(
deserializer: DeserializationStrategy<T>,
packet: ByteReadPacket,
length: Int = packet.remaining.toInt()
): T {
return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer) return JceDecoder(JceInput(packet, length.toLong())).decode(deserializer)
} }
@ -761,7 +816,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} }
} }
internal inline fun <R> Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? { internal inline fun <R> JceOld.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? {
// println("skipping to $tag start") // println("skipping to $tag start")
while (true) { while (true) {
if (isEndOfInput) { // 读不了了 if (isEndOfInput) { // 读不了了
@ -793,32 +848,3 @@ internal inline fun <R> Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead)
currentJceHead = readHeadOrNull() currentJceHead = readHeadOrNull()
} }
} }
@UseExperimental(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("unreachable")
}
return "JceHead(tag=$tag, type=$type($typeString))"
}
}

View File

@ -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
* *
* 代码复制自 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
@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
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
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
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
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
} }
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
}

View File

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

View File

@ -0,0 +1,241 @@
/*
* 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()
}
}

View File

@ -0,0 +1,73 @@
/*
* 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
}
}

View File

@ -0,0 +1,105 @@
/*
* 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))"
}
}

View File

@ -7,6 +7,9 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:JvmName("SerializationUtils")
@file:JvmMultifileClass
package net.mamoe.mirai.qqandroid.io.serialization package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.core.* import kotlinx.io.core.*
@ -15,20 +18,29 @@ 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.toUHexString import net.mamoe.mirai.utils.io.readPacketExact
import net.mamoe.mirai.utils.io.toReadPacket
import kotlin.jvm.JvmMultifileClass
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(
@ -36,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))
} }
/** /**
@ -66,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))

View File

@ -1,79 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid.message
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
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.MsgComm
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.SourceMsg
internal inline class MessageSourceFromServer(
val delegate: ImMsgBody.SourceMsg
) : MessageSource {
override val messageUid: Long get() = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
override val senderId: Long get() = delegate.senderUin
override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
override fun toString(): String = ""
}
internal inline class MessageSourceFromMsg(
val delegate: MsgComm.Msg
) : MessageSource {
override val messageUid: Long get() = delegate.msgBody.richText.attr!!.random.toLong()
override val sourceMessage: MessageChain get() = delegate.toMessageChain()
override val senderId: Long get() = delegate.msgHead.fromUin
override val groupId: Long get() = delegate.msgHead.groupInfo!!.groupCode
fun toJceData(): ImMsgBody.SourceMsg {
val groupUin = Group.calculateGroupUinByGroupCode(delegate.msgHead.groupInfo!!.groupCode)
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = groupUin,
flag = 1,
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = messageUid
).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 = messageUid, // ok
groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = delegate.msgBody.richText.elems.also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
override fun toString(): String = ""
}

View File

@ -0,0 +1,283 @@
/*
* 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.message
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.event.subscribingGetAsync
import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.message.data.MessageSource
import net.mamoe.mirai.message.data.messageRandom
import net.mamoe.mirai.message.data.sequenceId
import net.mamoe.mirai.qqandroid.io.serialization.loadAs
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.MsgComm
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.utils.MiraiExperimentalAPI
internal class MessageSourceFromServer(
val delegate: ImMsgBody.SourceMsg
) : MessageSource {
override val time: Long get() = delegate.time.toLong() and 0xFFFFFFFF
override val originalMessage: MessageChain by lazy {
delegate.toMessageChain()
}
override val id: Long
get() = (delegate.origSeqs?.firstOrNull()
?: error("cannot find sequenceId from ImMsgBody.SourceMsg")).toLong().shl(32) or
delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()).origUids!!.and(0xFFFFFFFF)
override val toUin: Long get() = delegate.toUin // always 0
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
// override val sourceMessage: MessageChain get() = delegate.toMessageChain()
override val senderId: Long get() = delegate.senderUin
override val groupId: Long get() = Group.calculateGroupCodeByGroupUin(delegate.toUin)
override fun toString(): String = ""
}
internal class MessageSourceFromMsg(
val delegate: MsgComm.Msg
) : MessageSource {
override val time: Long get() = delegate.msgHead.msgTime.toLong() and 0xFFFFFFFF
override val id: Long =
delegate.msgHead.msgSeq.toLong().shl(32) or
delegate.msgBody.richText.attr!!.random.toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
override val toUin: Long get() = delegate.msgHead.toUin
override val senderId: Long get() = delegate.msgHead.fromUin
override val groupId: Long get() = delegate.msgHead.groupInfo?.groupCode ?: 0
override val originalMessage: MessageChain by lazy {
delegate.toMessageChain()
}
fun toJceData(): ImMsgBody.SourceMsg {
return if (groupId == 0L) {
toJceDataImplForFriend()
} else toJceDataImplForGroup()
}
val elems by lazy {
delegate.msgBody.richText.elems.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
}
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = delegate.msgHead.toUin,
flag = 1,
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = SourceMsg.ResvAttr(
origUids = messageRandom.toLong() and 0xffFFffFF
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = delegate.msgHead.fromUin, // qq
toUin = delegate.msgHead.toUin, // 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 = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = listOf(delegate.msgHead.msgSeq),
senderUin = delegate.msgHead.fromUin,
toUin = 0,
flag = 1,
elems = delegate.msgBody.richText.elems,
type = 0,
time = delegate.msgHead.msgTime,
pbReserve = EMPTY_BYTE_ARRAY,
srcMsg = EMPTY_BYTE_ARRAY
)
}
override fun toString(): String = ""
}
internal abstract class MessageSourceFromSend : MessageSource {
abstract override val originalMessage: MessageChain
fun toJceData(): ImMsgBody.SourceMsg {
return if (groupId == 0L) {
toJceDataImplForFriend()
} else toJceDataImplForGroup()
}
private val elems by lazy {
originalMessage.toRichTextElems(groupId != 0L)
}
private fun toJceDataImplForFriend(): ImMsgBody.SourceMsg {
val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF)
return ImMsgBody.SourceMsg(
origSeqs = listOf(sequenceId),
senderUin = senderId,
toUin = toUin,
flag = 1,
elems = elems,
type = 0,
time = time.toInt(),
pbReserve = SourceMsg.ResvAttr(
origUids = messageUid
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = senderId, // qq
toUin = toUin, // group
msgType = 9, // 82?
c2cCmd = 11,
msgSeq = sequenceId,
msgTime = time.toInt(),
msgUid = messageUid, // ok
// groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
private fun toJceDataImplForGroup(): ImMsgBody.SourceMsg {
return ImMsgBody.SourceMsg(
origSeqs = listOf(sequenceId),
senderUin = senderId,
toUin = toUin,
flag = 1,
elems = elems,
type = 0,
time = time.toInt(),
pbReserve = SourceMsg.ResvAttr(
origUids = messageRandom.toLong() and 0xffFFffFF
).toByteArray(SourceMsg.ResvAttr.serializer()),
srcMsg = MsgComm.Msg(
msgHead = MsgComm.MsgHead(
fromUin = senderId, // qq
toUin = toUin, // group
msgType = 82, // 82?
c2cCmd = 1,
msgSeq = sequenceId,
msgTime = time.toInt(),
msgUid = messageRandom.toLong() and 0xffFFffFF, // ok
groupInfo = MsgComm.GroupInfo(groupCode = groupId),
isSrcMsg = true
),
msgBody = ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = elems.toMutableList().also {
if (it.last().elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2()))
}
)
)
).toByteArray(MsgComm.Msg.serializer())
)
}
}
internal class MessageSourceFromSendFriend(
val messageRandom: Int,
override val time: Long,
override val senderId: Long,
override val toUin: Long,
override val groupId: Long,
val sequenceId: Int,
override val originalMessage: MessageChain
) : MessageSourceFromSend() {
@OptIn(ExperimentalCoroutinesApi::class)
override val id: Long
get() = sequenceId.toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF)
override suspend fun ensureSequenceIdAvailable() {
// nothing to do
}
override fun toString(): String {
return ""
}
}
internal class MessageSourceFromSendGroup(
val messageRandom: Int,
override val time: Long,
override val senderId: Long,
override val toUin: Long,
override val groupId: Long,
override val originalMessage: MessageChain
) : MessageSourceFromSend() {
private lateinit var sequenceIdDeferred: Deferred<Int>
@OptIn(ExperimentalCoroutinesApi::class)
override val id: Long
get() = sequenceIdDeferred.getCompleted().toLong().shl(32) or
messageRandom.toLong().and(0xFFFFFFFF)
@OptIn(MiraiExperimentalAPI::class)
internal fun startWaitingSequenceId(coroutineScope: CoroutineScope) {
sequenceIdDeferred =
coroutineScope.subscribingGetAsync<OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt, Int>(
timeoutMillis = 3000
) {
if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) {
it.sequenceId
} else null
}
}
override suspend fun ensureSequenceIdAvailable() {
sequenceIdDeferred.join()
}
override fun toString(): String {
return ""
}
}

View File

@ -9,28 +9,35 @@
package net.mamoe.mirai.qqandroid.message package net.mamoe.mirai.qqandroid.message
import kotlinx.io.core.readUInt import kotlinx.io.core.*
import net.mamoe.mirai.LowLevelAPI
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.MiraiDebugAPI import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.discardExact
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
private val AT_BUF_1 = byteArrayOf(0x00, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00)
private val AT_BUF_2 = ByteArray(2)
internal fun At.toJceData(): ImMsgBody.Text { internal fun At.toJceData(): ImMsgBody.Text {
val text = this.toString()
return ImMsgBody.Text( return ImMsgBody.Text(
str = this.toString(), str = text,
attr6Buf = AT_BUF_1 + this.target.toInt().toByteArray() + AT_BUF_2 attr6Buf = buildPacket {
// MessageForText$AtTroopMemberInfo
writeShort(1) // const
writeShort(0) // startPos
writeShort(text.length.toShort()) // textLen
writeByte(0) // flag, may=1
writeInt(target.toInt()) // uin
writeShort(0) // const
}.readBytes()
) )
} }
internal fun NotOnlineImageFromFile.toJceData(): ImMsgBody.NotOnlineImage { internal fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage {
return ImMsgBody.NotOnlineImage( return ImMsgBody.NotOnlineImage(
filePath = this.filepath, filePath = this.filepath,
resId = this.resourceId, resId = this.resourceId,
@ -86,7 +93,17 @@ _400Height=0x000000EB(235)
pbReserve=<Empty ByteArray> pbReserve=<Empty ByteArray>
} }
*/ */
internal fun CustomFaceFromFile.toJceData(): ImMsgBody.CustomFace { val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes()
internal fun Face.toJceData(): ImMsgBody.Face {
return ImMsgBody.Face(
index = this.id,
old = (0x1445 - 4 + this.id).toShort().toByteArray(),
buf = FACE_BUF
)
}
internal fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace {
return ImMsgBody.CustomFace( return ImMsgBody.CustomFace(
filePath = this.filepath, filePath = this.filepath,
fileId = this.fileId, fileId = this.fileId,
@ -189,30 +206,75 @@ notOnlineImage=NotOnlineImage#2050019814 {
private val atAllData = ImMsgBody.Elem( private val atAllData = ImMsgBody.Elem(
text = ImMsgBody.Text( text = ImMsgBody.Text(
str = "@全体成员", str = "@全体成员",
attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes() attr6Buf = buildPacket {
// MessageForText$AtTroopMemberInfo
writeShort(1) // const
writeShort(0) // startPos
writeShort("@全体成员".length.toShort()) // textLen
writeByte(1) // flag, may=1
writeInt(0) // uin
writeShort(0) // const
}.readBytes()
) )
) )
internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> { @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList<ImMsgBody.Elem> {
val elements = mutableListOf<ImMsgBody.Elem>() val elements = mutableListOf<ImMsgBody.Elem>()
if (this.any<QuoteReply>()) { if (this.any<QuoteReply>()) {
when (val source = this[QuoteReply].source) { when (val source = this[QuoteReply].source) {
is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate)) is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
is MessageSourceFromMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData())) is MessageSourceFromMsg -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
is MessageSourceFromSend -> elements.add(ImMsgBody.Elem(srcMsg = source.toJceData()))
else -> error("unsupported MessageSource implementation: ${source::class.simpleName}") else -> error("unsupported MessageSource implementation: ${source::class.simpleName}")
} }
} }
this.forEach {
fun transformOneMessage(it: Message) {
when (it) { when (it) {
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue))) is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
is At -> elements.add(ImMsgBody.Elem(text = it.toJceData())) is At -> {
is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData())) elements.add(ImMsgBody.Elem(text = it.toJceData()))
is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate)) elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate)) }
is NotOnlineImageFromFile -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData())) 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 OnlineGroupImageImpl -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is OnlineFriendImageImpl -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
is OfflineFriendImage -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
is AtAll -> elements.add(atAllData) is AtAll -> elements.add(atAllData)
is Face -> elements.add(ImMsgBody.Elem(face = it.toJceData()))
is QuoteReplyToSend -> {
if (forGroup) {
check(it is QuoteReplyToSend.ToGroup) {
"sending a quote to group using QuoteReplyToSend.ToFriend"
}
if (it.sender is Member) {
transformOneMessage(it.createAt())
}
transformOneMessage(" ".toMessage())
}
}
is QuoteReply, is QuoteReply,
is MessageSource -> { is MessageSource -> {
@ -220,18 +282,20 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
else -> error("unsupported message type: ${it::class.simpleName}") else -> error("unsupported message type: ${it::class.simpleName}")
} }
} }
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
} }
internal class CustomFaceFromServer( internal class OnlineGroupImageImpl(
internal val delegate: ImMsgBody.CustomFace internal val delegate: ImMsgBody.CustomFace
) : CustomFace() { ) : OnlineGroupImage() {
override val filepath: String get() = delegate.filePath override val filepath: String = delegate.filePath
override val fileId: Int get() = delegate.fileId override val fileId: Int get() = delegate.fileId
override val serverIp: Int get() = delegate.serverIp override val serverIp: Int get() = delegate.serverIp
override val serverPort: Int get() = delegate.serverPort override val serverPort: Int get() = delegate.serverPort
@ -247,20 +311,22 @@ internal class CustomFaceFromServer(
override val size: Int get() = delegate.size override val size: Int get() = delegate.size
override val original: Int get() = delegate.origin override val original: Int get() = delegate.origin
override val pbReserve: ByteArray get() = delegate.pbReserve override val pbReserve: ByteArray get() = delegate.pbReserve
override val imageId: String get() = delegate.filePath override val imageId: String = ExternalImage.generateImageId(delegate.md5, imageType)
override val originUrl: String
get() = "http://gchat.qpic.cn" + delegate.origUrl
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return other is CustomFaceFromServer && other.filepath == this.filepath && other.md5.contentEquals(this.md5) return other is OnlineGroupImageImpl && other.filepath == this.filepath && other.md5.contentEquals(this.md5)
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return filepath.hashCode() + 31 * md5.hashCode() return imageId.hashCode() + 31 * md5.hashCode()
} }
} }
internal class NotOnlineImageFromServer( internal class OnlineFriendImageImpl(
internal val delegate: ImMsgBody.NotOnlineImage internal val delegate: ImMsgBody.NotOnlineImage
) : NotOnlineImage() { ) : OnlineFriendImage() {
override val resourceId: String get() = delegate.resId override val resourceId: String get() = delegate.resId
override val md5: ByteArray get() = delegate.picMd5 override val md5: ByteArray get() = delegate.picMd5
override val filepath: String get() = delegate.filePath override val filepath: String get() = delegate.filePath
@ -272,52 +338,72 @@ internal class NotOnlineImageFromServer(
override val downloadPath: String get() = delegate.downloadPath override val downloadPath: String get() = delegate.downloadPath
override val fileId: Int get() = delegate.fileId override val fileId: Int get() = delegate.fileId
override val original: Int get() = delegate.original override val original: Int get() = delegate.original
override val originUrl: String
get() = "http://c2cpicdw.qpic.cn" + this.delegate.origUrl
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return other is NotOnlineImageFromServer && other.resourceId == this.resourceId && other.md5.contentEquals(this.md5) return other is OnlineFriendImageImpl && other.resourceId == this.resourceId && other.md5
.contentEquals(this.md5)
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return resourceId.hashCode() + 31 * md5.hashCode() return imageId.hashCode() + 31 * md5.hashCode()
} }
} }
@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
val message = MessageChain(initialCapacity = elements.size + 1) return buildMessageChain(elements.size + 1) {
message.add(MessageSourceFromMsg(delegate = this)) +MessageSourceFromMsg(delegate = this@toMessageChain)
elements.joinToMessageChain(message) elements.joinToMessageChain(this)
return message }.removeAtIfHasQuoteReply()
} }
// These two functions are not the same. // These two functions are not identical, dont combine.
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain { internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
val elements = this.elems!! val elements = this.elems!!
val message = MessageChain(initialCapacity = elements.size + 1) return buildMessageChain(elements.size + 1) {
message.add(MessageSourceFromServer(delegate = this)) +MessageSourceFromServer(delegate = this@toMessageChain)
elements.joinToMessageChain(message) elements.joinToMessageChain(this)
return message }.removeAtIfHasQuoteReply()
} }
private fun MessageChain.removeAtIfHasQuoteReply(): MessageChain =
this
/*
if (this.any<QuoteReply>()) {
var removed = false
this.filter {
if (it is At && !removed) {
false
} else {
removed = true
true
}
}.asMessageChain()
} else this*/
@UseExperimental(MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class) @OptIn(
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) { MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class, LowLevelAPI::class
)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) {
this.forEach { this.forEach {
when { when {
it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg))) it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg)))
it.notOnlineImage != null -> message.add(NotOnlineImageFromServer(it.notOnlineImage)) it.notOnlineImage != null -> message.add(OnlineFriendImageImpl(it.notOnlineImage))
it.customFace != null -> message.add(CustomFaceFromServer(it.customFace)) it.customFace != null -> message.add(OnlineGroupImageImpl(it.customFace))
it.face != null -> message.add(Face(it.face.index))
it.text != null -> { it.text != null -> {
if (it.text.attr6Buf.isEmpty()) { if (it.text.attr6Buf.isEmpty()) {
message.add(it.text.str.toMessage()) message.add(it.text.str.toMessage())
} else { } else {
// 00 01 00 00 00 05 01 00 00 00 00 00 00 all // 00 01 00 00 00 05 01 00 00 00 00 00 00 all
//00 01 00 00 00 0A 00 3E 03 3F A2 00 00 one // 00 01 00 00 00 0A 00 3E 03 3F A2 00 00 one/nick
// 00 01 00 00 00 07 00 44 71 47 90 00 00 one/groupCard
val id: Long val id: Long
it.text.attr6Buf.read { it.text.attr6Buf.read {
discardExact(7) discardExact(7)
@ -326,7 +412,24 @@ internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) {
if (id == 0L) { if (id == 0L) {
message.add(AtAll) message.add(AtAll)
} else { } else {
message.add(At(id, it.text.str)) message.add(At._lowLevelConstructAtInstance(id, it.text.str))
}
}
}
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}"
}
} }
} }
} }

View File

@ -20,11 +20,9 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.use 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.BroadcastControllable import net.mamoe.mirai.event.*
import net.mamoe.mirai.event.CancellableEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOfflineEvent
import net.mamoe.mirai.event.events.BotOnlineEvent
import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.BotNetworkHandler
import net.mamoe.mirai.network.WrongPasswordException import net.mamoe.mirai.network.WrongPasswordException
import net.mamoe.mirai.qqandroid.FriendInfoImpl import net.mamoe.mirai.qqandroid.FriendInfoImpl
@ -43,14 +41,14 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.login.WtLogin
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
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.readPacket import net.mamoe.mirai.utils.io.readPacketExact
import net.mamoe.mirai.utils.io.useBytes import net.mamoe.mirai.utils.io.useBytes
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.jvm.Volatile 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,17 +65,20 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
private val packetReceiveLock: Mutex = Mutex() private val packetReceiveLock: Mutex = Mutex()
private fun startPacketReceiverJobOrKill(cancelCause: CancellationException? = null): Job { private suspend fun startPacketReceiverJobOrKill(cancelCause: CancellationException? = null): Job {
_packetReceiverJob?.cancel(cancelCause) _packetReceiverJob?.cancel(cancelCause)
_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) {
BotOfflineEvent.Dropped(bot).broadcast() if (this@QQAndroidBotNetworkHandler.isActive) {
bot.launch { BotOfflineEvent.Dropped(bot, e).broadcast() }
}
return@launch return@launch
} }
packetReceiveLock.withLock { packetReceiveLock.withLock {
@ -87,25 +88,42 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}.also { _packetReceiverJob = it } }.also { _packetReceiverJob = it }
} }
override suspend fun relogin() { private fun startHeartbeatJobOrKill(cancelCause: CancellationException? = null): Job {
heartbeatJob?.cancel() heartbeatJob?.cancel(cancelCause)
return this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) {
while (this.isActive) {
delay(bot.configuration.heartbeatPeriodMillis)
val failException = doHeartBeat()
if (failException != null) {
delay(bot.configuration.firstReconnectDelayMillis)
bot.launch { BotOfflineEvent.Dropped(bot, failException).broadcast() }
return@launch
}
}
}.also { heartbeatJob = it }
}
override suspend fun relogin(cause: Throwable?) {
heartbeatJob?.cancel(CancellationException("relogin", cause))
heartbeatJob?.join()
if (::channel.isInitialized) { if (::channel.isInitialized) {
if (channel.isOpen) { if (channel.isOpen) {
kotlin.runCatching { kotlin.runCatching {
registerClientOnline() registerClientOnline(500)
}.exceptionOrNull() ?: return }.exceptionOrNull() ?: return
logger.info("Cannot do fast relogin. Trying slow relogin") logger.info("Cannot do fast relogin. Trying slow relogin")
} }
channel.close() channel.close()
} }
channel = PlatformSocket() channel = PlatformSocket()
// TODO: 2020/2/14 连接多个服务器 // TODO: 2020/2/14 连接多个服务器, #52
withTimeoutOrNull(3000) { withTimeoutOrNull(3000) {
channel.connect("113.96.13.208", 8080) channel.connect("113.96.13.208", 8080)
} ?: error("timeout connecting server") } ?: error("timeout connecting server")
startPacketReceiverJobOrKill(CancellationException("reconnect")) logger.info("Connected to server 113.96.13.208:8080")
startPacketReceiverJobOrKill(CancellationException("relogin", cause))
// logger.info("Trying login")
var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect() var response: WtLogin.Login.LoginPacketResponse = WtLogin.Login.SubCommand9(bot.client).sendAndExpect()
mainloop@ while (true) { mainloop@ while (true) {
when (response) { when (response) {
@ -116,21 +134,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
is WtLogin.Login.LoginPacketResponse.Captcha -> when (response) { is WtLogin.Login.LoginPacketResponse.Captcha -> when (response) {
is WtLogin.Login.LoginPacketResponse.Captcha.Picture -> { is WtLogin.Login.LoginPacketResponse.Captcha.Picture -> {
var result = response.data.withUse { var result = bot.configuration.loginSolver.onSolvePicCaptcha(bot, response.data)
bot.configuration.loginSolver.onSolvePicCaptcha(bot, this)
}
if (result == null || result.length != 4) { if (result == null || result.length != 4) {
//refresh captcha //refresh captcha
result = "ABCD" result = "ABCD"
} }
response = WtLogin.Login.SubCommand2.SubmitPictureCaptcha(bot.client, response.sign, result).sendAndExpect() response = WtLogin.Login.SubCommand2.SubmitPictureCaptcha(bot.client, response.sign, result)
.sendAndExpect()
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
} }
@ -156,55 +170,73 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
// println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}") // println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}")
registerClientOnline() registerClientOnline()
startHeartbeatJobOrKill()
} }
private suspend fun registerClientOnline() { private suspend fun registerClientOnline(timeoutMillis: Long = 3000) {
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>() StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>(timeoutMillis)
} }
// caches // caches
private val _pendingEnabled = atomic(true) private val _pendingEnabled = atomic(true)
internal val pendingEnabled get() = _pendingEnabled.value internal val pendingEnabled get() = _pendingEnabled.value
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? = LockFreeLinkedList() internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? =
LockFreeLinkedList()
@UseExperimental(MiraiExperimentalAPI::class, ExperimentalTime::class) @OptIn(MiraiExperimentalAPI::class, ExperimentalTime::class)
override suspend fun init(): Unit = coroutineScope { override suspend fun init(): Unit = coroutineScope {
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect() check(bot.isActive) { "bot is dead therefore network can't init" }
check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't init" }
bot.qqs.delegate.clear() bot.friends.delegate.clear()
bot.groups.delegate.clear() bot.groups.delegate.clear()
val friendListJob = launch { val friendListJob = launch {
try { lateinit var loadFriends: suspend () -> Unit
// 不要用 fun, 不要 join declaration, 不要用 val, 编译失败警告
loadFriends = suspend loadFriends@{
logger.info("开始加载好友信息") logger.info("开始加载好友信息")
var currentFriendCount = 0 var currentFriendCount = 0
var totalFriendCount: Short var totalFriendCount: Short
while (true) { while (true) {
val data = FriendList.GetFriendGroupList( val data = runCatching {
FriendList.GetFriendGroupList(
bot.client, bot.client,
currentFriendCount, currentFriendCount,
150, 150,
0, 0,
0 0
).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 5000, retry = 2) ).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 5000, retry = 2)
}.getOrElse {
logger.error("无法加载好友列表", it)
this@QQAndroidBotNetworkHandler.launch { delay(10.secondsToMillis); loadFriends() }
logger.error("稍后重试加载好友列表")
return@loadFriends
}
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, FriendInfoImpl(it))).also { bot.friends.delegate.addLast(
QQImpl(
bot,
bot.coroutineContext,
it.friendUin,
FriendInfoImpl(it)
)
).also {
currentFriendCount++ currentFriendCount++
} }
} }
logger.verbose("正在加载好友列表 ${currentFriendCount}/${totalFriendCount}") logger.verbose { "正在加载好友列表 ${currentFriendCount}/${totalFriendCount}" }
if (currentFriendCount >= totalFriendCount) { if (currentFriendCount >= totalFriendCount) {
break break
} }
// delay(200) // delay(200)
} }
logger.info("好友列表加载完成, 共 ${currentFriendCount}") logger.info { "好友列表加载完成, 共 ${currentFriendCount}" }
} catch (e: Exception) {
logger.error("加载好友列表失败|一般这是由于加载过于频繁导致/将以热加载方式加载好友列表")
} }
loadFriends()
} }
val groupJob = launch { val groupJob = launch {
@ -214,15 +246,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2) .sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
troopListData.groups.forEach { troopNum -> troopListData.groups.forEach { troopNum ->
launch { // 别用 fun, 别 val, 编译失败警告
try { lateinit var loadGroup: suspend () -> Unit
loadGroup = suspend {
tryNTimesOrException(3) {
bot.groups.delegate.addLast( bot.groups.delegate.addLast(
@Suppress("DuplicatedCode") @Suppress("DuplicatedCode")
GroupImpl( (GroupImpl(
bot = bot, bot = bot,
coroutineContext = bot.coroutineContext, coroutineContext = bot.coroutineContext,
id = troopNum.groupCode, id = troopNum.groupCode,
groupInfo = bot.queryGroupInfo(troopNum.groupCode).apply { groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
this as GroupInfoImpl this as GroupInfoImpl
if (this.delegate.groupName == null) { if (this.delegate.groupName == null) {
@ -239,51 +274,78 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
this.delegate.groupCode = troopNum.groupCode this.delegate.groupCode = troopNum.groupCode
}, },
members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin) members = bot._lowLevelQueryGroupMemberList(
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
) )
))
) )
}?.let {
logger.error { "${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试" }
logger.error(it)
this@QQAndroidBotNetworkHandler.launch {
delay(10_000)
loadGroup()
}
}
Unit // 别删, 编译失败警告
}
launch {
loadGroup()
}
}
logger.info { "群组列表与群成员加载完成, 共 ${troopListData.groups.size}" }
} catch (e: Exception) { } catch (e: Exception) {
logger.error("${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试") logger.error { "加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表" }
logger.error(e)
}
}
}
logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}")
} catch (e: Exception) {
logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
logger.error(e) logger.error(e)
} }
} }
joinAll(friendListJob, groupJob) joinAll(friendListJob, groupJob)
heartbeatJob = this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) { withTimeoutOrNull(5000) {
while (this.isActive) { lateinit var listener: Listener<PacketReceivedEvent>
delay(bot.configuration.heartbeatPeriodMillis) listener = this.subscribeAlways {
val failException = doHeartBeat() if (it.packet is MessageSvc.PbGetMsg.GetMsgSuccess) {
if (failException != null) { listener.complete()
delay(bot.configuration.firstReconnectDelayMillis)
close()
BotOfflineEvent.Dropped(bot).broadcast()
}
} }
} }
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
} ?: error("timeout syncing friend message history")
bot.firstLoginSucceed = true bot.firstLoginSucceed = true
_pendingEnabled.value = false _pendingEnabled.value = false
pendingIncomingPackets?.forEach { pendingIncomingPackets?.forEach {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
KnownPacketFactories.handleIncomingPacket(it as KnownPacketFactories.IncomingPacket<Packet>, bot, it.flag2, it.consumer) KnownPacketFactories.handleIncomingPacket(
it as KnownPacketFactories.IncomingPacket<Packet>,
bot,
it.flag2,
it.consumer
)
} }
pendingIncomingPackets = null // release val list = pendingIncomingPackets
pendingIncomingPackets = null // release, help gc
list?.clear() // help gc
Unit BotOnlineEvent(bot).broadcast()
Unit // dont remove. can help type inference
} }
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,
@ -301,10 +363,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
*/ */
@Volatile @Volatile
private var cachedPacketTimeoutJob: Job? = null private var cachedPacketTimeoutJob: Job? = null
/** /**
* 缓存的包 * 缓存的包
*/ */
private val cachedPacket: AtomicRef<ByteReadPacket?> = atomic(null) private val cachedPacket: AtomicRef<ByteReadPacket?> = atomic(null)
/** /**
* 缓存的包还差多少长度 * 缓存的包还差多少长度
*/ */
@ -316,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(
start = CoroutineStart.ATOMIC
) {
try {
input.use { parsePacket(it) } input.use { parsePacket(it) }
} catch (e: Exception) {
// 傻逼协程吞异常
logger.error(e)
}
} }
} }
@ -334,35 +405,55 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
// with generic type, less mistakes // with generic type, less mistakes
private suspend inline fun <P : Packet> generifiedParsePacket(input: Input) { private suspend fun <P : Packet?> generifiedParsePacket(input: Input) {
KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int -> KnownPacketFactories.parseIncomingPacket(
handlePacket(packetFactory, packet, commandName, sequenceId) bot,
input
) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
if (packet is MultiPacket<*>) { if (packet is MultiPacket<*>) {
packet.forEach { packet.forEach {
handlePacket(null, it, commandName, sequenceId) handlePacket(null, it, commandName, sequenceId)
} }
} }
handlePacket(packetFactory, packet, commandName, sequenceId)
} }
} }
/** /**
* 处理解析完成的包. * 处理解析完成的包.
*/ */
suspend fun <P : Packet> handlePacket(packetFactory: PacketFactory<P>?, packet: P, commandName: String, sequenceId: Int) { suspend fun <P : Packet?> handlePacket(
packetFactory: PacketFactory<P>?,
packet: P,
commandName: String,
sequenceId: Int
) {
// highest priority: pass to listeners (attached by sendAndExpect). // highest priority: pass to listeners (attached by sendAndExpect).
if (packet != null && (bot.logger.isEnabled || logger.isEnabled)) {
val logMessage = "Received: ${packet.toString().replace("\n", """\n""").replace("\r", "")}"
if (packet is Event) {
bot.logger.verbose(logMessage)
} else logger.verbose(logMessage)
}
packetListeners.forEach { listener -> packetListeners.forEach { listener ->
if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) { if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) {
listener.complete(packet) listener.complete(packet)
} }
} }
// check top-level cancelling packetFactory?.run {
if (PacketReceivedEvent(packet).broadcast().isCancelled) { when (this) {
is OutgoingPacketFactory<P> -> bot.handle(packet)
is IncomingPacketFactory<P> -> bot.handle(packet, sequenceId)?.sendWithoutExpect()
}
}
if (packet != null && PacketReceivedEvent(packet).broadcast().isCancelled) {
return return
} }
// broadcast
if (packet is Event) { if (packet is Event) {
if (packet is BroadcastControllable) { if (packet is BroadcastControllable) {
if (packet.shouldBroadcast) packet.broadcast() if (packet.shouldBroadcast) packet.broadcast()
@ -372,15 +463,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
if (packet is CancellableEvent && packet.isCancelled) return if (packet is CancellableEvent && packet.isCancelled) return
} }
logger.info("Received: ${packet.toString().replace("\n", """\n""").replace("\r", "")}")
packetFactory?.run {
when (this) {
is OutgoingPacketFactory<P> -> bot.handle(packet)
is IncomingPacketFactory<P> -> bot.handle(packet, sequenceId)?.sendWithoutExpect()
}
}
} }
/** /**
@ -404,7 +486,7 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
} }
// 循环所有完整的包 // 循环所有完整的包
while (rawInput.remaining >= length) { while (rawInput.remaining >= length) {
parsePacketAsync(rawInput.readPacket(length)) parsePacketAsync(rawInput.readPacketExact(length))
if (rawInput.remaining == 0L) { if (rawInput.remaining == 0L) {
cachedPacket.value = null // 表示包长度正好 cachedPacket.value = null // 表示包长度正好
@ -467,63 +549,77 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
* 发送一个包, 但不期待任何返回. * 发送一个包, 但不期待任何返回.
*/ */
suspend fun OutgoingPacket.sendWithoutExpect() { suspend fun OutgoingPacket.sendWithoutExpect() {
logger.info("Send: ${this.commandName}") check(bot.isActive) { "bot is dead therefore can't send any packet" }
check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't send any packet" }
logger.verbose("Send: ${this.commandName}")
withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) {
PacketLogger.debug { "Channel sending: $commandName" }
channel.send(delegate) channel.send(delegate)
PacketLogger.debug { "Channel send done: $commandName" }
} }
} }
class TimeoutException(override val message: String?) : Exception()
/** /**
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms) * 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
* *
* @param retry 当不为 0 时将使用 [ByteArrayPool] 缓存. 因此若非必要, 请不要允许 retry * @param retry 当不为 0 时将使用 [ByteArrayPool] 缓存. 因此若非必要, 请不要允许 retry
*/ */
suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 0): E { suspend fun <E : Packet> OutgoingPacket.sendAndExpect(timeoutMillis: Long = 3000, retry: Int = 0): E {
require(timeoutMillis > 0) { "timeoutMillis must > 0" } require(timeoutMillis > 100) { "timeoutMillis must > 100" }
require(retry >= 0) { "retry must >= 0" } require(retry >= 0) { "retry must >= 0" }
var lastException: Exception? = null check(bot.isActive) { "bot is dead therefore can't send any packet" }
check(this@QQAndroidBotNetworkHandler.isActive) { "network is dead therefore can't send any packet" }
suspend fun doSendAndReceive(handler: PacketListener, data: Any, length: Int): E {
val result = async {
withTimeoutOrNull(3000) {
withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) {
PacketLogger.debug { "Channel sending: $commandName" }
when (data) {
is ByteArray -> channel.send(data, 0, length)
is ByteReadPacket -> channel.send(data)
else -> error("Internal error: unexpected data type: ${data::class.simpleName}")
}
PacketLogger.debug { "Channel send done: $commandName" }
}
} ?: return@async "timeout sending packet $commandName"
logger.verbose("Send done: $commandName")
withTimeoutOrNull(timeoutMillis) {
handler.await()
// 不要 `withTimeout`. timeout 的报错会不正常.
} ?: return@async "timeout receiving response of $commandName"
}
@Suppress("UNCHECKED_CAST")
when (val value = result.await()) {
is String -> throw TimeoutException(value)
else -> return value as E
}
}
if (retry == 0) { if (retry == 0) {
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId) val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
packetListeners.addLast(handler) packetListeners.addLast(handler)
try { try {
withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { return doSendAndReceive(handler, delegate, 0) // no need
channel.send(delegate)
}
logger.info("Send: ${this.commandName}")
return withTimeoutOrNull(timeoutMillis) {
@Suppress("UNCHECKED_CAST")
handler.await() as E
// 不要 `withTimeout`. timeout 的异常会不知道去哪了.
} ?: net.mamoe.mirai.qqandroid.utils.inline {
error("timeout when receiving response of $commandName")
}
} finally { } finally {
packetListeners.remove(handler) packetListeners.remove(handler)
} }
} else this.delegate.useBytes { data, length -> } else this.delegate.useBytes { data, length ->
repeat(retry + 1) { return tryNTimes(retry + 1) {
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId) val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
packetListeners.addLast(handler) packetListeners.addLast(handler)
try { try {
withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) { doSendAndReceive(handler, data, length)
channel.send(data, 0, length)
}
logger.info("Send: ${this.commandName}")
return withTimeoutOrNull(timeoutMillis) {
@Suppress("UNCHECKED_CAST")
handler.await() as E
// 不要 `withTimeout`. timeout 的异常会不知道去哪了.
} ?: net.mamoe.mirai.qqandroid.utils.inline {
error("timeout when receiving response of $commandName")
}
} catch (e: Exception) {
lastException = e
} finally { } finally {
packetListeners.remove(handler) packetListeners.remove(handler)
} }
} }
throw lastException!!
} }
} }
@ -534,8 +630,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
internal inner class PacketListener( // callback internal inner class PacketListener( // callback
val commandName: String, val commandName: String,
val sequenceId: Int val sequenceId: Int
) : CompletableDeferred<Packet> by CompletableDeferred(supervisor) { ) : CompletableDeferred<Packet?> by CompletableDeferred(supervisor) {
fun filter(commandName: String, sequenceId: Int) = this.commandName == commandName && this.sequenceId == sequenceId fun filter(commandName: String, sequenceId: Int) =
this.commandName == commandName && this.sequenceId == sequenceId
} }
override fun close(cause: Throwable?) { override fun close(cause: Throwable?) {
@ -545,5 +642,5 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
super.close(cause) super.close(cause)
} }
override suspend fun awaitDisconnection() = supervisor.join() override suspend fun join() = supervisor.join()
} }

View File

@ -7,12 +7,13 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.network package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.*
import kotlinx.io.core.toByteArray
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
@ -20,12 +21,10 @@ import net.mamoe.mirai.qqandroid.QQAndroidBot
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.qqandroid.network.protocol.packet.PacketLogger import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger
import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv import net.mamoe.mirai.qqandroid.network.protocol.packet.Tlv
import net.mamoe.mirai.utils.DeviceInfo
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.cryptor.ECDH import net.mamoe.mirai.utils.cryptor.ECDH
import net.mamoe.mirai.utils.cryptor.decryptBy import net.mamoe.mirai.utils.cryptor.TEA
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
/* /*
@ -41,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,
@ -72,7 +71,7 @@ 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(TEA.decrypt(data, value, size).also { PacketLogger.verbose { "成功使用 $key 解密" } })
} }
} }
return null return null
@ -101,8 +100,8 @@ internal open class QQAndroidClient(
var openAppId: Long = 715019303L var openAppId: Long = 715019303L
val apkVersionName: ByteArray get() = "8.2.0".toByteArray() val apkVersionName: ByteArray get() = "8.2.7".toByteArray()
val buildVer: String get() = "8.2.0.1296" val buildVer: String get() = "8.2.7.4410"
private val messageSequenceId: AtomicInt = atomic(22911) private val messageSequenceId: AtomicInt = atomic(22911)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2) internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
@ -113,7 +112,7 @@ internal open class QQAndroidClient(
private val highwayDataTransSequenceIdForGroup: AtomicInt = atomic(87017) private val highwayDataTransSequenceIdForGroup: AtomicInt = atomic(87017)
internal fun nextHighwayDataTransSequenceIdForGroup(): Int = highwayDataTransSequenceIdForGroup.getAndAdd(2) internal fun nextHighwayDataTransSequenceIdForGroup(): Int = highwayDataTransSequenceIdForGroup.getAndAdd(2)
private val highwayDataTransSequenceIdForFriend: AtomicInt = atomic(40717) private val highwayDataTransSequenceIdForFriend: AtomicInt = atomic(43973)
internal fun nextHighwayDataTransSequenceIdForFriend(): Int = highwayDataTransSequenceIdForFriend.getAndAdd(2) internal fun nextHighwayDataTransSequenceIdForFriend(): Int = highwayDataTransSequenceIdForFriend.getAndAdd(2)
val appClientVersion: Int = 0 val appClientVersion: Int = 0
@ -123,7 +122,7 @@ internal open class QQAndroidClient(
val apkSignatureMd5: ByteArray = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes() val apkSignatureMd5: ByteArray = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes()
/** /**
* 协议版本?, 8.2.0 的为 8001 * 协议版本?, 8.2.7 的为 8001
*/ */
val protocolVersion: Short = 8001 val protocolVersion: Short = 8001
@ -159,16 +158,18 @@ 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
var t528: ByteArray? = null var t528: ByteArray? = null
/** /**
* t108 时更新 * t108 时更新
*/ */
var ksid: ByteArray = "|454001228437590|A8.2.0.27f6ea96".toByteArray() var ksid: ByteArray = "|454001228437590|A8.2.7.27f6ea96".toByteArray()
/** /**
* t186 * t186
*/ */
@ -190,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(
@ -314,6 +316,10 @@ 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 = kotlinx.io.core.String(this.readUShortLVByteArray())
internal inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt())
internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) = internal fun parsePSKeyMapAndPt4TokenMap(data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap) =
data.read { data.read {
repeat(readShort().toInt()) { repeat(readShort().toInt()) {

View File

@ -1,86 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.qqandroid.network.highway
import kotlinx.io.core.*
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.CSDataHighwayHead
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
object Highway {
fun RequestDataTrans(
uin: Long,
command: String,
sequenceId: Int,
appId: Int = 537062845,
dataFlag: Int = 4096,
commandId: Int,
localId: Int = 2052,
uKey: ByteArray,
data: Input,
dataSize: Int,
md5: ByteArray
): ByteReadPacket {
require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" }
require(data !is ByteReadPacket || data.remaining.toInt() == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as ByteReadPacket).remaining}" }
require(data !is IoBuffer || data.readRemaining == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as IoBuffer).readRemaining}" }
val dataHighwayHead = CSDataHighwayHead.DataHighwayHead(
version = 1,
uin = uin.toString(),
command = command,
seq = sequenceId,
retryTimes = 0,
appid = appId,
dataflag = dataFlag,
commandId = commandId,
localeId = localId
)
val segHead = CSDataHighwayHead.SegHead(
datalength = dataSize,
filesize = dataSize.toLong(),
serviceticket = uKey,
md5 = md5,
fileMd5 = md5,
flag = 0,
rtcode = 0
)
//println(data.readBytes().toUHexString())
return Codec.buildC2SData(dataHighwayHead, segHead, EMPTY_BYTE_ARRAY, null, data, dataSize)
}
private object Codec {
fun buildC2SData(
dataHighwayHead: CSDataHighwayHead.DataHighwayHead,
segHead: CSDataHighwayHead.SegHead,
extendInfo: ByteArray,
loginSigHead: CSDataHighwayHead.LoginSigHead?,
body: Input,
bodySize: Int
): ByteReadPacket {
val head = CSDataHighwayHead.ReqDataHighwayHead(
msgBasehead = dataHighwayHead,
msgSeghead = segHead,
reqExtendinfo = extendInfo,
msgLoginSigHead = loginSigHead
).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer())
return buildPacket {
writeByte(40)
writeInt(head.size)
writeInt(bodySize)
writeFully(head)
check(body.copyTo(this).toInt() == bodySize) { "bad body size" }
writeByte(41)
}
}
}
}

View File

@ -16,30 +16,36 @@ 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.ByteWriteChannel
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream
import kotlinx.io.core.Input import kotlinx.io.core.Input
import kotlinx.io.core.discardExact
import kotlinx.io.core.readAvailable import kotlinx.io.core.readAvailable
import kotlinx.io.core.use import kotlinx.io.core.use
import kotlinx.io.pool.useInstance 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.qqandroid.network.protocol.packet.withUse
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.discardExact import net.mamoe.mirai.utils.io.withUse
import kotlinx.serialization.InternalSerializationApi
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
@Suppress("SpellCheckingInspection") @Suppress("SpellCheckingInspection")
internal suspend inline fun HttpClient.postImage( internal suspend fun HttpClient.postImage(
htcmd: String, htcmd: String,
uin: Long, uin: Long,
groupcode: Long?, groupcode: Long?,
imageInput: Input, imageInput: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor
inputSize: Long, inputSize: Long,
uKeyHex: String uKeyHex: String
): Boolean = try { ): Boolean = post<HttpStatusCode> {
post<HttpStatusCode> {
url { url {
protocol = URLProtocol.HTTP protocol = URLProtocol.HTTP
host = "htdata2.qq.com" host = "htdata2.qq.com"
@ -63,54 +69,62 @@ internal suspend inline 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) {
is Input -> {
var size: Int var size: Int
while (imageInput.readAvailable(buffer).also { size = it } != 0) { while (imageInput.readAvailable(buffer).also { size = it } > 0) {
channel.writeFully(buffer, 0, size) channel.writeFully(buffer, 0, size)
channel.flush()
}
}
is ByteReadChannel -> imageInput.copyAndClose(channel)
is InputStream -> {
var size: Int
while (imageInput.read(buffer).also { size = it } > 0) {
channel.writeFully(buffer, 0, size)
channel.flush()
}
}
else -> error("unsupported imageInput: ${imageInput::class.simpleName}")
} }
} }
} }
} }
} == HttpStatusCode.OK } == HttpStatusCode.OK
} finally {
imageInput.close()
}
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal object HighwayHelper { internal object HighwayHelper {
@OptIn(InternalCoroutinesApi::class)
suspend fun uploadImage( suspend fun uploadImage(
client: QQAndroidClient, client: QQAndroidClient,
serverIp: String, serverIp: String,
serverPort: Int, serverPort: Int,
uKey: ByteArray, uKey: ByteArray,
imageInput: Input, imageInput: Any,
inputSize: Int, inputSize: Int,
md5: ByteArray, fileMd5: ByteArray,
commandId: Int // group=2, friend=1 commandId: Int // group=2, friend=1
) { ) {
require(md5.size == 16) { "bad md5. Required size=16, got ${md5.size}" } require(imageInput is Input || imageInput is InputStream || imageInput is ByteReadChannel) { "unsupported imageInput: ${imageInput::class.simpleName}" }
require(fileMd5.size == 16) { "bad md5. Required size=16, got ${fileMd5.size}" }
require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" } require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" }
require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" } require(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
val socket = PlatformSocket() val socket = PlatformSocket()
socket.connect(serverIp, serverPort) socket.connect(serverIp, serverPort)
socket.use { socket.use {
socket.send( createImageDataPacketSequence(
Highway.RequestDataTrans( client = client,
uin = client.uin,
command = "PicUp.DataUp", command = "PicUp.DataUp",
sequenceId = commandId = commandId,
if (commandId == 2) client.nextHighwayDataTransSequenceIdForGroup()
else client.nextHighwayDataTransSequenceIdForFriend(),
uKey = uKey, uKey = uKey,
data = imageInput, data = imageInput,
dataSize = inputSize, dataSize = inputSize,
md5 = md5, fileMd5 = fileMd5
commandId = commandId ).collect {
) socket.send(it)
)
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
socket.read().withUse { socket.read().withUse {
discardExact(1) discardExact(1)
@ -122,3 +136,4 @@ internal object HighwayHelper {
} }
} }
} }
}

View File

@ -0,0 +1,100 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
package net.mamoe.mirai.qqandroid.network.highway
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.io.ByteReadChannel
import kotlinx.io.InputStream
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.Input
import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.io.serialization.toByteArray
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
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.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.*
import kotlinx.serialization.InternalSerializationApi
import net.mamoe.mirai.utils.MiraiPlatformUtils
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal fun createImageDataPacketSequence( // RequestDataTrans
client: QQAndroidClient,
command: String,
appId: Int = 537062845,
dataFlag: Int = 4096,
commandId: Int,
localId: Int = 2052,
uKey: ByteArray,
data: Any,
dataSize: Int,
fileMd5: ByteArray,
sizePerPacket: Int = 8192
): Flow<ByteReadPacket> {
ByteArrayPool.checkBufferSize(sizePerPacket)
require(data is Input || data is InputStream || data is ByteReadChannel) { "unsupported data: ${data::class.simpleName}" }
require(uKey.size == 128) { "bad uKey. Required size=128, got ${uKey.size}" }
require(data !is ByteReadPacket || data.remaining.toInt() == dataSize) { "bad input. given dataSize=$dataSize, but actual readRemaining=${(data as ByteReadPacket).remaining}" }
val flow = when (data) {
is ByteReadPacket -> data.chunkedFlow(sizePerPacket)
is Input -> data.chunkedFlow(sizePerPacket)
is ByteReadChannel -> data.chunkedFlow(sizePerPacket)
is InputStream -> data.chunkedFlow(sizePerPacket)
else -> error("unreachable code")
}
var offset = 0L
return flow.map { chunkedInput ->
buildPacket {
val head = CSDataHighwayHead.ReqDataHighwayHead(
msgBasehead = CSDataHighwayHead.DataHighwayHead(
version = 1,
uin = client.uin.toString(),
command = command,
seq = if (commandId == 2) client.nextHighwayDataTransSequenceIdForGroup()
else client.nextHighwayDataTransSequenceIdForFriend(),
retryTimes = 0,
appid = appId,
dataflag = dataFlag,
commandId = commandId,
localeId = localId
),
msgSeghead = CSDataHighwayHead.SegHead(
// cacheAddr = 812157193,
datalength = chunkedInput.bufferSize,
dataoffset = offset,
filesize = dataSize.toLong(),
serviceticket = uKey,
md5 = MiraiPlatformUtils.md5(chunkedInput.buffer, 0, chunkedInput.bufferSize),
fileMd5 = fileMd5,
flag = 0,
rtcode = 0
),
reqExtendinfo = EMPTY_BYTE_ARRAY,
msgLoginSigHead = null
).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer())
offset += chunkedInput.bufferSize
writeByte(40)
writeInt(head.size)
writeInt(chunkedInput.bufferSize)
writeFully(head)
writeFully(chunkedInput.buffer, 0, chunkedInput.bufferSize)
writeByte(41)
}
}
}

View File

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

View File

@ -9,172 +9,172 @@
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 ModifyGroupCardReq( internal class ModifyGroupCardReq(
@SerialId(0) val dwZero: Long, @JceId(0) val dwZero: Long,
@SerialId(1) val dwGroupCode: Long, @JceId(1) val dwGroupCode: Long,
@SerialId(2) val dwNewSeq: Long, @JceId(2) val dwNewSeq: Long,
@SerialId(3) val vecUinInfo: List<stUinInfo> @JceId(3) val vecUinInfo: List<stUinInfo>
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class stUinInfo( internal class stUinInfo(
@SerialId(0) val dwuin: Long, @JceId(0) val dwuin: Long,
@SerialId(1) val dwFlag: Long, @JceId(1) val dwFlag: Long,
@SerialId(2) val sName: String = "", @JceId(2) val sName: String = "",
@SerialId(3) val gender: Byte, @JceId(3) val gender: Byte,
@SerialId(4) val sPhone: String = "", @JceId(4) val sPhone: String = "",
@SerialId(5) val sEmail: String = "", @JceId(5) val sEmail: String = "",
@SerialId(6) val sRemark: String = "" @JceId(6) val sRemark: String = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class GetFriendListReq( internal class GetFriendListReq(
@SerialId(0) val reqtype: Int? = null, @JceId(0) val reqtype: Int? = null,
@SerialId(1) val ifReflush: Byte? = null, @JceId(1) val ifReflush: Byte? = null,
@SerialId(2) val uin: Long? = null, @JceId(2) val uin: Long? = null,
@SerialId(3) val startIndex: Short? = null, @JceId(3) val startIndex: Short? = null,
@SerialId(4) val getfriendCount: Short? = null, @JceId(4) val getfriendCount: Short? = null,
@SerialId(5) val groupid: Byte? = null, @JceId(5) val groupid: Byte? = null,
@SerialId(6) val ifGetGroupInfo: Byte? = null, @JceId(6) val ifGetGroupInfo: Byte? = null,
@SerialId(7) val groupstartIndex: Byte? = null, @JceId(7) val groupstartIndex: Byte? = null,
@SerialId(8) val getgroupCount: Byte? = null, @JceId(8) val getgroupCount: Byte? = null,
@SerialId(9) val ifGetMSFGroup: Byte? = null, @JceId(9) val ifGetMSFGroup: Byte? = null,
@SerialId(10) val ifShowTermType: Byte? = null, @JceId(10) val ifShowTermType: Byte? = null,
@SerialId(11) val version: Long? = null, @JceId(11) val version: Long? = null,
@SerialId(12) val uinList: List<Long>? = null, @JceId(12) val uinList: List<Long>? = null,
@SerialId(13) val eAppType: Int = 0, @JceId(13) val eAppType: Int = 0,
@SerialId(14) val ifGetDOVId: Byte? = null, @JceId(14) val ifGetDOVId: Byte? = null,
@SerialId(15) val ifGetBothFlag: Byte? = null, @JceId(15) val ifGetBothFlag: Byte? = null,
@SerialId(16) val vec0xd50Req: ByteArray? = null, @JceId(16) val vec0xd50Req: ByteArray? = null,
@SerialId(17) val vec0xd6bReq: ByteArray? = null, @JceId(17) val vec0xd6bReq: ByteArray? = null,
@SerialId(18) val vecSnsTypelist: List<Long>? = null @JceId(18) val vecSnsTypelist: List<Long>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class GetFriendListResp( internal class GetFriendListResp(
@SerialId(0) val reqtype: Int, @JceId(0) val reqtype: Int,
@SerialId(1) val ifReflush: Byte, @JceId(1) val ifReflush: Byte,
@SerialId(2) val uin: Long, @JceId(2) val uin: Long,
@SerialId(3) val startIndex: Short, @JceId(3) val startIndex: Short,
@SerialId(4) val getfriendCount: Short, @JceId(4) val getfriendCount: Short,
@SerialId(5) val totoalFriendCount: Short, @JceId(5) val totoalFriendCount: Short,
@SerialId(6) val friendCount: Short, @JceId(6) val friendCount: Short,
@SerialId(7) val vecFriendInfo: List<FriendInfo>? = null, @JceId(7) val vecFriendInfo: List<FriendInfo>? = null,
@SerialId(8) val groupid: Byte? = null, @JceId(8) val groupid: Byte? = null,
@SerialId(9) val ifGetGroupInfo: Byte, @JceId(9) val ifGetGroupInfo: Byte,
@SerialId(10) val groupstartIndex: Byte? = null, @JceId(10) val groupstartIndex: Byte? = null,
@SerialId(11) val getgroupCount: Byte? = null, @JceId(11) val getgroupCount: Byte? = null,
@SerialId(12) val totoalGroupCount: Short? = null, @JceId(12) val totoalGroupCount: Short? = null,
@SerialId(13) val groupCount: Byte? = null, @JceId(13) val groupCount: Byte? = null,
@SerialId(14) val vecGroupInfo: List<GroupInfo>? = null, @JceId(14) val vecGroupInfo: List<GroupInfo>? = null,
@SerialId(15) val result: Int, @JceId(15) val result: Int,
@SerialId(16) val errorCode: Short? = null, @JceId(16) val errorCode: Short? = null,
@SerialId(17) val onlineFriendCount: Short? = null, @JceId(17) val onlineFriendCount: Short? = null,
@SerialId(18) val serverTime: Long? = null, @JceId(18) val serverTime: Long? = null,
@SerialId(19) val sqqOnLineCount: Short? = null, @JceId(19) val sqqOnLineCount: Short? = null,
@SerialId(20) val vecMSFGroupInfo: List<GroupInfo>? = null, @JceId(20) val vecMSFGroupInfo: List<GroupInfo>? = null,
@SerialId(21) val respType: Byte? = null, @JceId(21) val respType: Byte? = null,
@SerialId(22) val hasOtherRespFlag: Byte? = null, @JceId(22) val hasOtherRespFlag: Byte? = null,
@SerialId(23) val stSelfInfo: FriendInfo? = null, @JceId(23) val stSelfInfo: FriendInfo? = null,
@SerialId(24) val showPcIcon: Byte? = null, @JceId(24) val showPcIcon: Byte? = null,
@SerialId(25) val wGetExtSnsRspCode: Short? = null, @JceId(25) val wGetExtSnsRspCode: Short? = null,
@SerialId(26) val stSubSrvRspCode: FriendListSubSrvRspCode? = null @JceId(26) val stSubSrvRspCode: FriendListSubSrvRspCode? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class FriendListSubSrvRspCode( internal class FriendListSubSrvRspCode(
@SerialId(0) val wGetMutualMarkRspCode: Short? = null, @JceId(0) val wGetMutualMarkRspCode: Short? = null,
@SerialId(1) val wGetIntimateInfoRspCode: Short? = null @JceId(1) val wGetIntimateInfoRspCode: Short? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class FriendInfo( internal class FriendInfo(
@SerialId(0) val friendUin: Long, @JceId(0) val friendUin: Long,
@SerialId(1) val groupId: Byte, @JceId(1) val groupId: Byte,
@SerialId(2) val faceId: Short, @JceId(2) val faceId: Short,
@SerialId(3) val remark: String = "", @JceId(3) val remark: String = "",
@SerialId(4) val sqqtype: Byte, @JceId(4) val sqqtype: Byte,
@SerialId(5) val status: Byte = 20, @JceId(5) val status: Byte = 20,
@SerialId(6) val memberLevel: Byte? = null, @JceId(6) val memberLevel: Byte? = null,
@SerialId(7) val isMqqOnLine: Byte? = null, @JceId(7) val isMqqOnLine: Byte? = null,
@SerialId(8) val sqqOnLineState: Byte? = null, @JceId(8) val sqqOnLineState: Byte? = null,
@SerialId(9) val isIphoneOnline: Byte? = null, @JceId(9) val isIphoneOnline: Byte? = null,
@SerialId(10) val detalStatusFlag: Byte? = null, @JceId(10) val detalStatusFlag: Byte? = null,
@SerialId(11) val sqqOnLineStateV2: Byte? = null, @JceId(11) val sqqOnLineStateV2: Byte? = null,
@SerialId(12) val sShowName: String? = "", @JceId(12) val sShowName: String? = "",
@SerialId(13) val isRemark: Byte? = null, @JceId(13) val isRemark: Byte? = null,
@SerialId(14) val nick: String? = "", @JceId(14) val nick: String? = "",
@SerialId(15) val specialFlag: Byte? = null, @JceId(15) val specialFlag: Byte? = null,
@SerialId(16) val vecIMGroupID: ByteArray? = null, @JceId(16) val vecIMGroupID: ByteArray? = null,
@SerialId(17) val vecMSFGroupID: ByteArray? = null, @JceId(17) val vecMSFGroupID: ByteArray? = null,
@SerialId(18) val iTermType: Int? = null, @JceId(18) val iTermType: Int? = null,
@SerialId(19) val oVipInfo: VipBaseInfo? = null, @JceId(19) val oVipInfo: VipBaseInfo? = null,
@SerialId(20) val network: Byte? = null, @JceId(20) val network: Byte? = null,
@SerialId(21) val vecRing: ByteArray? = null, @JceId(21) val vecRing: ByteArray? = null,
@SerialId(22) val uAbiFlag: Long? = null, @JceId(22) val uAbiFlag: Long? = null,
@SerialId(23) val ulFaceAddonId: Long? = null, @JceId(23) val ulFaceAddonId: Long? = null,
@SerialId(24) val eNetworkType: Int? = 0, @JceId(24) val eNetworkType: Int? = 0,
@SerialId(25) val uVipFont: Long? = null, @JceId(25) val uVipFont: Long? = null,
@SerialId(26) val eIconType: Int? = 0, @JceId(26) val eIconType: Int? = 0,
@SerialId(27) val termDesc: String? = "", @JceId(27) val termDesc: String? = "",
@SerialId(28) val uColorRing: Long? = null, @JceId(28) val uColorRing: Long? = null,
@SerialId(29) val apolloFlag: Byte? = null, @JceId(29) val apolloFlag: Byte? = null,
@SerialId(30) val uApolloTimestamp: Long? = null, @JceId(30) val uApolloTimestamp: Long? = null,
@SerialId(31) val sex: Byte? = null, @JceId(31) val sex: Byte? = null,
@SerialId(32) val uFounderFont: Long? = null, @JceId(32) val uFounderFont: Long? = null,
@SerialId(33) val eimId: String? = "", @JceId(33) val eimId: String? = "",
@SerialId(34) val eimMobile: String? = "", @JceId(34) val eimMobile: String? = "",
@SerialId(35) val olympicTorch: Byte? = null, @JceId(35) val olympicTorch: Byte? = null,
@SerialId(36) val uApolloSignTime: Long? = null, @JceId(36) val uApolloSignTime: Long? = null,
@SerialId(37) val uLaviUin: Long? = null, @JceId(37) val uLaviUin: Long? = null,
@SerialId(38) val uTagUpdateTime: Long? = null, @JceId(38) val uTagUpdateTime: Long? = null,
@SerialId(39) val uGameLastLoginTime: Long? = null, @JceId(39) val uGameLastLoginTime: Long? = null,
@SerialId(40) val uGameAppid: Long? = null, @JceId(40) val uGameAppid: Long? = null,
@SerialId(41) val vecCardID: ByteArray? = null, @JceId(41) val vecCardID: ByteArray? = null,
@SerialId(42) val ulBitSet: Long? = null, @JceId(42) val ulBitSet: Long? = null,
@SerialId(43) val kingOfGloryFlag: Byte? = null, @JceId(43) val kingOfGloryFlag: Byte? = null,
@SerialId(44) val ulKingOfGloryRank: Long? = null, @JceId(44) val ulKingOfGloryRank: Long? = null,
@SerialId(45) val masterUin: String? = "", @JceId(45) val masterUin: String? = "",
@SerialId(46) val uLastMedalUpdateTime: Long? = null, @JceId(46) val uLastMedalUpdateTime: Long? = null,
@SerialId(47) val uFaceStoreId: Long? = null, @JceId(47) val uFaceStoreId: Long? = null,
@SerialId(48) val uFontEffect: Long? = null, @JceId(48) val uFontEffect: Long? = null,
@SerialId(49) val sDOVId: String? = "", @JceId(49) val sDOVId: String? = "",
@SerialId(50) val uBothFlag: Long? = null, @JceId(50) val uBothFlag: Long? = null,
@SerialId(51) val centiShow3DFlag: Byte? = null, @JceId(51) val centiShow3DFlag: Byte? = null,
@SerialId(52) val vecIntimateInfo: ByteArray? = null, @JceId(52) val vecIntimateInfo: ByteArray? = null,
@SerialId(53) val showNameplate: Byte? = null, @JceId(53) val showNameplate: Byte? = null,
@SerialId(54) val newLoverDiamondFlag: Byte? = null, @JceId(54) val newLoverDiamondFlag: Byte? = null,
@SerialId(55) val vecExtSnsFrdData: ByteArray? = null, @JceId(55) val vecExtSnsFrdData: ByteArray? = null,
@SerialId(56) val vecMutualMarkData: ByteArray? = null @JceId(56) val vecMutualMarkData: ByteArray? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class VipBaseInfo( internal class VipBaseInfo(
@SerialId(0) val mOpenInfo: Map<Int, VipOpenInfo> @JceId(0) val mOpenInfo: Map<Int, VipOpenInfo>
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class VipOpenInfo( internal class VipOpenInfo(
@SerialId(0) val open: Boolean, @JceId(0) val open: Boolean,
@SerialId(1) val iVipType: Int = -1, @JceId(1) val iVipType: Int = -1,
@SerialId(2) val iVipLevel: Int = -1, @JceId(2) val iVipLevel: Int = -1,
@SerialId(3) val iVipFlag: Int? = null, @JceId(3) val iVipFlag: Int? = null,
@SerialId(4) val nameplateId: Long? = null @JceId(4) val nameplateId: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class GroupInfo( internal class GroupInfo(
@SerialId(0) val groupId: Byte, @JceId(0) val groupId: Byte,
@SerialId(1) val groupname: String = "", @JceId(1) val groupname: String = "",
@SerialId(2) val friendCount: Int, @JceId(2) val friendCount: Int,
@SerialId(3) val onlineFriendCount: Int, @JceId(3) val onlineFriendCount: Int,
@SerialId(4) val seqid: Byte? = null, @JceId(4) val seqid: Byte? = null,
@SerialId(5) val sqqOnLineCount: Int? = null @JceId(5) val sqqOnLineCount: Int? = null
) : JceStruct ) : JceStruct

View File

@ -9,250 +9,250 @@
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
internal class OnlinePushPack { internal class OnlinePushPack {
@Serializable @Serializable
internal class DelMsgInfo( internal class DelMsgInfo(
@SerialId(0) val fromUin: Long, @JceId(0) val fromUin: Long,
@SerialId(1) val uMsgTime: Long, @JceId(1) val uMsgTime: Long,
@SerialId(2) val shMsgSeq: Short, @JceId(2) val shMsgSeq: Short,
@SerialId(3) val vMsgCookies: ByteArray? = null, @JceId(3) val vMsgCookies: ByteArray? = null,
@SerialId(4) val wCmd: Short? = null, @JceId(4) val wCmd: Short? = null,
@SerialId(5) val uMsgType: Long? = null, @JceId(5) val uMsgType: Long? = null,
@SerialId(6) val uAppId: Long? = null, @JceId(6) val uAppId: Long? = null,
@SerialId(7) val sendTime: Long? = null, @JceId(7) val sendTime: Long? = null,
@SerialId(8) val ssoSeq: Int? = null, @JceId(8) val ssoSeq: Int? = null,
@SerialId(9) val ssoIp: Int? = null, @JceId(9) val ssoIp: Int? = null,
@SerialId(10) val clientIp: Int? = null @JceId(10) val clientIp: Int? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class DeviceInfo( internal class DeviceInfo(
@SerialId(0) val netType: Byte? = null, @JceId(0) val netType: Byte? = null,
@SerialId(1) val devType: String? = "", @JceId(1) val devType: String? = "",
@SerialId(2) val oSVer: String? = "", @JceId(2) val oSVer: String? = "",
@SerialId(3) val vendorName: String? = "", @JceId(3) val vendorName: String? = "",
@SerialId(4) val vendorOSName: String? = "", @JceId(4) val vendorOSName: String? = "",
@SerialId(5) val iOSIdfa: String? = "" @JceId(5) val iOSIdfa: String? = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class Name( internal class Name(
@SerialId(0) val fromUin: Long, @JceId(0) val fromUin: Long,
@SerialId(1) val uMsgTime: Long, @JceId(1) val uMsgTime: Long,
@SerialId(2) val shMsgType: Short, @JceId(2) val shMsgType: Short,
@SerialId(3) val shMsgSeq: Short, @JceId(3) val shMsgSeq: Short,
@SerialId(4) val msg: String = "", @JceId(4) val msg: String = "",
@SerialId(5) val uRealMsgTime: Int? = null, @JceId(5) val uRealMsgTime: Int? = null,
@SerialId(6) val vMsg: ByteArray? = null, @JceId(6) val vMsg: ByteArray? = null,
@SerialId(7) val uAppShareID: Long? = null, @JceId(7) val uAppShareID: Long? = null,
@SerialId(8) val vMsgCookies: ByteArray? = null, @JceId(8) val vMsgCookies: ByteArray? = null,
@SerialId(9) val vAppShareCookie: ByteArray? = null, @JceId(9) val vAppShareCookie: ByteArray? = null,
@SerialId(10) val msgUid: Long? = null, @JceId(10) val msgUid: Long? = null,
@SerialId(11) val lastChangeTime: Long? = 1L, @JceId(11) val lastChangeTime: Long? = 1L,
@SerialId(12) val vCPicInfo: List<CPicInfo>? = null, @JceId(12) val vCPicInfo: List<CPicInfo>? = null,
@SerialId(13) val stShareData: ShareData? = null, @JceId(13) val stShareData: ShareData? = null,
@SerialId(14) val fromInstId: Long? = null, @JceId(14) val fromInstId: Long? = null,
@SerialId(15) val vRemarkOfSender: ByteArray? = null, @JceId(15) val vRemarkOfSender: ByteArray? = null,
@SerialId(16) val fromMobile: String? = "", @JceId(16) val fromMobile: String? = "",
@SerialId(17) val fromName: String? = "", @JceId(17) val fromName: String? = "",
@SerialId(18) val vNickName: List<String>? = null, @JceId(18) val vNickName: List<String>? = null,
@SerialId(19) val stC2CTmpMsgHead: TempMsgHead? = null @JceId(19) val stC2CTmpMsgHead: TempMsgHead? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class SvcReqPushMsg( internal class SvcReqPushMsg(
@SerialId(0) val uin: Long, @JceId(0) val uin: Long,
@SerialId(1) val uMsgTime: Long, @JceId(1) val uMsgTime: Long,
@SerialId(2) val vMsgInfos: List<MsgInfo>, @JceId(2) val vMsgInfos: List<MsgInfo>,
@SerialId(3) val svrip: Int? = 0, @JceId(3) val svrip: Int? = 0,
@SerialId(4) val vSyncCookie: ByteArray? = null, @JceId(4) val vSyncCookie: ByteArray? = null,
@SerialId(5) val vUinPairMsg: List<UinPairMsg>? = null, @JceId(5) val vUinPairMsg: List<UinPairMsg>? = null,
@SerialId(6) val mPreviews: Map<String, ByteArray>? = null @JceId(6) val mPreviews: Map<String, ByteArray>? = null
// @SerialId(7) val wUserActive: Int? = null, // @SerialId(7) val wUserActive: Int? = null,
//@SerialId(12) val wGeneralFlag: Int? = null //@SerialId(12) val wGeneralFlag: Int? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class SvcRespPushMsg( internal class SvcRespPushMsg(
@SerialId(0) val uin: Long, @JceId(0) val uin: Long,
@SerialId(1) val vDelInfos: List<DelMsgInfo>, @JceId(1) val vDelInfos: List<DelMsgInfo>,
@SerialId(2) val svrip: Int, @JceId(2) val svrip: Int,
@SerialId(3) val pushToken: ByteArray? = null, @JceId(3) val pushToken: ByteArray? = null,
@SerialId(4) val serviceType: Int? = null, @JceId(4) val serviceType: Int? = null,
@SerialId(5) val deviceInfo: DeviceInfo? = null @JceId(5) val deviceInfo: DeviceInfo? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class UinPairMsg( internal class UinPairMsg(
@SerialId(1) val uLastReadTime: Long? = null, @JceId(1) val uLastReadTime: Long? = null,
@SerialId(2) val peerUin: Long? = null, @JceId(2) val peerUin: Long? = null,
@SerialId(3) val uMsgCompleted: Long? = null, @JceId(3) val uMsgCompleted: Long? = null,
@SerialId(4) val vMsgInfos: List<MsgInfo>? = null @JceId(4) val vMsgInfos: List<MsgInfo>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210( internal class MsgType0x210(
@SerialId(0) val uSubMsgType: Long, @JceId(0) val uSubMsgType: Long,
@SerialId(1) val stMsgInfo0x2: MsgType0x210SubMsgType0x2? = null, @JceId(1) val stMsgInfo0x2: MsgType0x210SubMsgType0x2? = null,
@SerialId(3) val stMsgInfo0xa: MsgType0x210SubMsgType0xa? = null, @JceId(3) val stMsgInfo0xa: MsgType0x210SubMsgType0xa? = null,
@SerialId(4) val stMsgInfo0xe: MsgType0x210SubMsgType0xe? = null, @JceId(4) val stMsgInfo0xe: MsgType0x210SubMsgType0xe? = null,
@SerialId(5) val stMsgInfo0x13: MsgType0x210SubMsgType0x13? = null, @JceId(5) val stMsgInfo0x13: MsgType0x210SubMsgType0x13? = null,
@SerialId(6) val stMsgInfo0x17: MsgType0x210SubMsgType0x17? = null, @JceId(6) val stMsgInfo0x17: MsgType0x210SubMsgType0x17? = null,
@SerialId(7) val stMsgInfo0x20: MsgType0x210SubMsgType0x20? = null, @JceId(7) val stMsgInfo0x20: MsgType0x210SubMsgType0x20? = null,
@SerialId(8) val stMsgInfo0x1d: MsgType0x210SubMsgType0x1d? = null, @JceId(8) val stMsgInfo0x1d: MsgType0x210SubMsgType0x1d? = null,
@SerialId(9) val stMsgInfo0x24: MsgType0x210SubMsgType0x24? = null, @JceId(9) val stMsgInfo0x24: MsgType0x210SubMsgType0x24? = null,
@SerialId(10) val vProtobuf: ByteArray? = null @JceId(10) val vProtobuf: ByteArray? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0x13( internal class MsgType0x210SubMsgType0x13(
@SerialId(0) val uint32SrcAppId: Long? = null, @JceId(0) val uint32SrcAppId: Long? = null,
@SerialId(1) val uint32SrcInstId: Long? = null, @JceId(1) val uint32SrcInstId: Long? = null,
@SerialId(2) val uint32DstAppId: Long? = null, @JceId(2) val uint32DstAppId: Long? = null,
@SerialId(3) val uint32DstInstId: Long? = null, @JceId(3) val uint32DstInstId: Long? = null,
@SerialId(4) val uint64DstUin: Long? = null, @JceId(4) val uint64DstUin: Long? = null,
@SerialId(5) val uint64Sessionid: Long? = null, @JceId(5) val uint64Sessionid: Long? = null,
@SerialId(6) val uint32Size: Long? = null, @JceId(6) val uint32Size: Long? = null,
@SerialId(7) val uint32Index: Long? = null, @JceId(7) val uint32Index: Long? = null,
@SerialId(8) val uint32Type: Long? = null, @JceId(8) val uint32Type: Long? = null,
@SerialId(9) val buf: ByteArray? = null @JceId(9) val buf: ByteArray? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0x17( internal class MsgType0x210SubMsgType0x17(
@SerialId(0) val dwOpType: Long? = null, @JceId(0) val dwOpType: Long? = null,
@SerialId(1) val stAddGroup: AddGroup? = null, @JceId(1) val stAddGroup: AddGroup? = null,
@SerialId(2) val stDelGroup: DelGroup? = null, @JceId(2) val stDelGroup: DelGroup? = null,
@SerialId(3) val stModGroupName: ModGroupName? = null, @JceId(3) val stModGroupName: ModGroupName? = null,
@SerialId(4) val stModGroupSort: ModGroupSort? = null, @JceId(4) val stModGroupSort: ModGroupSort? = null,
@SerialId(5) val stModFriendGroup: ModFriendGroup? = null @JceId(5) val stModFriendGroup: ModFriendGroup? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class AddGroup( internal class AddGroup(
@SerialId(0) val dwGroupID: Long? = null, @JceId(0) val dwGroupID: Long? = null,
@SerialId(1) val dwSortID: Long? = null, @JceId(1) val dwSortID: Long? = null,
@SerialId(2) val groupName: String? = "" @JceId(2) val groupName: String? = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class DelGroup( internal class DelGroup(
@SerialId(0) val dwGroupID: Long? = null @JceId(0) val dwGroupID: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class ModFriendGroup( internal class ModFriendGroup(
@SerialId(0) val vMsgFrdGroup: List<FriendGroup>? = null @JceId(0) val vMsgFrdGroup: List<FriendGroup>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class FriendGroup( internal class FriendGroup(
@SerialId(0) val dwFuin: Long? = null, @JceId(0) val dwFuin: Long? = null,
@SerialId(1) val vOldGroupID: List<Long>? = null, @JceId(1) val vOldGroupID: List<Long>? = null,
@SerialId(2) val vNewGroupID: List<Long>? = null @JceId(2) val vNewGroupID: List<Long>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class ModGroupName( internal class ModGroupName(
@SerialId(0) val dwGroupID: Long? = null, @JceId(0) val dwGroupID: Long? = null,
@SerialId(1) val groupName: String? = "" @JceId(1) val groupName: String? = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class ModGroupSort( internal class ModGroupSort(
@SerialId(0) val vMsgGroupSort: List<GroupSort>? = null @JceId(0) val vMsgGroupSort: List<GroupSort>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class GroupSort( internal class GroupSort(
@SerialId(0) val dwGroupID: Long? = null, @JceId(0) val dwGroupID: Long? = null,
@SerialId(1) val dwSortID: Long? = null @JceId(1) val dwSortID: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0x1d( internal class MsgType0x210SubMsgType0x1d(
@SerialId(0) val dwOpType: Long? = null, @JceId(0) val dwOpType: Long? = null,
@SerialId(1) val dwUin: Long? = null, @JceId(1) val dwUin: Long? = null,
@SerialId(2) val dwID: Long? = null, @JceId(2) val dwID: Long? = null,
@SerialId(3) val value: String? = "" @JceId(3) val value: String? = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0x2( internal class MsgType0x210SubMsgType0x2(
@SerialId(0) val uSrcAppId: Long? = null, @JceId(0) val uSrcAppId: Long? = null,
@SerialId(1) val uSrcInstId: Long? = null, @JceId(1) val uSrcInstId: Long? = null,
@SerialId(2) val uDstAppId: Long? = null, @JceId(2) val uDstAppId: Long? = null,
@SerialId(3) val uDstInstId: Long? = null, @JceId(3) val uDstInstId: Long? = null,
@SerialId(4) val uDstUin: Long? = null, @JceId(4) val uDstUin: Long? = null,
@SerialId(5) val fileName: ByteArray? = null, @JceId(5) val fileName: ByteArray? = null,
@SerialId(6) val fileIndex: ByteArray? = null, @JceId(6) val fileIndex: ByteArray? = null,
@SerialId(7) val fileMd5: ByteArray? = null, @JceId(7) val fileMd5: ByteArray? = null,
@SerialId(8) val fileKey: ByteArray? = null, @JceId(8) val fileKey: ByteArray? = null,
@SerialId(9) val uServerIp: Long? = null, @JceId(9) val uServerIp: Long? = null,
@SerialId(10) val uServerPort: Long? = null, @JceId(10) val uServerPort: Long? = null,
@SerialId(11) val fileLen: Long? = null, @JceId(11) val fileLen: Long? = null,
@SerialId(12) val sessionId: Long? = null, @JceId(12) val sessionId: Long? = null,
@SerialId(13) val originfileMd5: ByteArray? = null, @JceId(13) val originfileMd5: ByteArray? = null,
@SerialId(14) val uOriginfiletype: Long? = null, @JceId(14) val uOriginfiletype: Long? = null,
@SerialId(15) val uSeq: Long? = null @JceId(15) val uSeq: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0x20( internal class MsgType0x210SubMsgType0x20(
@SerialId(0) val dwOpType: Long? = null, @JceId(0) val dwOpType: Long? = null,
@SerialId(1) val dwType: Long? = null, @JceId(1) val dwType: Long? = null,
@SerialId(2) val dwUin: Long? = null, @JceId(2) val dwUin: Long? = null,
@SerialId(3) val remaek: String? = "" @JceId(3) val remaek: String? = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0x24( internal class MsgType0x210SubMsgType0x24(
@SerialId(0) val vPluginNumList: List<PluginNum>? = null @JceId(0) val vPluginNumList: List<PluginNum>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class PluginNum( internal class PluginNum(
@SerialId(0) val dwID: Long? = null, @JceId(0) val dwID: Long? = null,
@SerialId(1) val dwNUm: Long? = null, @JceId(1) val dwNUm: Long? = null,
@SerialId(2) val flag: Byte? = null @JceId(2) val flag: Byte? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0xa( internal class MsgType0x210SubMsgType0xa(
@SerialId(0) val uSrcAppId: Long? = null, @JceId(0) val uSrcAppId: Long? = null,
@SerialId(1) val uSrcInstId: Long? = null, @JceId(1) val uSrcInstId: Long? = null,
@SerialId(2) val uDstAppId: Long? = null, @JceId(2) val uDstAppId: Long? = null,
@SerialId(3) val uDstInstId: Long? = null, @JceId(3) val uDstInstId: Long? = null,
@SerialId(4) val uDstUin: Long? = null, @JceId(4) val uDstUin: Long? = null,
@SerialId(5) val uType: Long? = null, @JceId(5) val uType: Long? = null,
@SerialId(6) val uServerIp: Long? = null, @JceId(6) val uServerIp: Long? = null,
@SerialId(7) val uServerPort: Long? = null, @JceId(7) val uServerPort: Long? = null,
@SerialId(8) val vUrlNotify: ByteArray? = null, @JceId(8) val vUrlNotify: ByteArray? = null,
@SerialId(9) val vTokenKey: ByteArray? = null, @JceId(9) val vTokenKey: ByteArray? = null,
@SerialId(10) val uFileLen: Long? = null, @JceId(10) val uFileLen: Long? = null,
@SerialId(11) val fileName: ByteArray? = null, @JceId(11) val fileName: ByteArray? = null,
@SerialId(12) val vMd5: ByteArray? = null, @JceId(12) val vMd5: ByteArray? = null,
@SerialId(13) val sessionId: Long? = null, @JceId(13) val sessionId: Long? = null,
@SerialId(14) val originfileMd5: ByteArray? = null, @JceId(14) val originfileMd5: ByteArray? = null,
@SerialId(15) val uOriginfiletype: Long? = null, @JceId(15) val uOriginfiletype: Long? = null,
@SerialId(16) val uSeq: Long? = null @JceId(16) val uSeq: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class MsgType0x210SubMsgType0xe( internal class MsgType0x210SubMsgType0xe(
@SerialId(0) val uint32SrcAppId: Long? = null, @JceId(0) val uint32SrcAppId: Long? = null,
@SerialId(1) val uint32SrcInstId: Long? = null, @JceId(1) val uint32SrcInstId: Long? = null,
@SerialId(2) val uint32DstAppId: Long? = null, @JceId(2) val uint32DstAppId: Long? = null,
@SerialId(3) val uint32DstInstId: Long? = null, @JceId(3) val uint32DstInstId: Long? = null,
@SerialId(4) val uint64DstUin: Long? = null, @JceId(4) val uint64DstUin: Long? = null,
@SerialId(5) val uint64Sessionid: Long? = null, @JceId(5) val uint64Sessionid: Long? = null,
@SerialId(6) val uint32Operate: Long? = null, @JceId(6) val uint32Operate: Long? = null,
@SerialId(7) val uint32Seq: Long? = null, @JceId(7) val uint32Seq: Long? = null,
@SerialId(8) val uint32Code: Long? = null, @JceId(8) val uint32Code: Long? = null,
@SerialId(9) val msg: String? = "" @JceId(9) val msg: String? = ""
) : JceStruct ) : JceStruct
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -9,183 +9,183 @@
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 GetTroopListReqV2Simplify( internal class GetTroopListReqV2Simplify(
@SerialId(0) val uin: Long, @JceId(0) val uin: Long,
@SerialId(1) val getMSFMsgFlag: Byte? = null, @JceId(1) val getMSFMsgFlag: Byte? = null,
@SerialId(2) val vecCookies: ByteArray? = null, @JceId(2) val vecCookies: ByteArray? = null,
@SerialId(3) val vecGroupInfo: List<StTroopNumSimplify>? = null, @JceId(3) val vecGroupInfo: List<StTroopNumSimplify>? = null,
@SerialId(4) val groupFlagExt: Byte? = null, @JceId(4) val groupFlagExt: Byte? = null,
@SerialId(5) val shVersion: Int? = null, @JceId(5) val shVersion: Int? = null,
@SerialId(6) val dwCompanyId: Long? = null, @JceId(6) val dwCompanyId: Long? = null,
@SerialId(7) val versionNum: Long? = null, @JceId(7) val versionNum: Long? = null,
@SerialId(8) val getLongGroupName: Byte? = null @JceId(8) val getLongGroupName: Byte? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class StTroopNumSimplify( internal class StTroopNumSimplify(
@SerialId(0) val groupCode: Long, @JceId(0) val groupCode: Long,
@SerialId(1) val dwGroupInfoSeq: Long? = null, @JceId(1) val dwGroupInfoSeq: Long? = null,
@SerialId(2) val dwGroupFlagExt: Long? = null, @JceId(2) val dwGroupFlagExt: Long? = null,
@SerialId(3) val dwGroupRankSeq: Long? = null @JceId(3) val dwGroupRankSeq: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class GetTroopListRespV2( internal class GetTroopListRespV2(
@SerialId(0) val uin: Long, @JceId(0) val uin: Long,
@SerialId(1) val troopCount: Short, @JceId(1) val troopCount: Short,
@SerialId(2) val result: Int, @JceId(2) val result: Int,
@SerialId(3) val errorCode: Short? = null, @JceId(3) val errorCode: Short? = null,
@SerialId(4) val vecCookies: ByteArray? = null, @JceId(4) val vecCookies: ByteArray? = null,
@SerialId(5) val vecTroopList: List<StTroopNum>? = null, @JceId(5) val vecTroopList: List<StTroopNum>? = null,
@SerialId(6) val vecTroopListDel: List<StTroopNum>? = null, @JceId(6) val vecTroopListDel: List<StTroopNum>? = null,
@SerialId(7) val vecTroopRank: List<StGroupRankInfo>? = null, @JceId(7) val vecTroopRank: List<StGroupRankInfo>? = null,
@SerialId(8) val vecFavGroup: List<StFavoriteGroup>? = null, @JceId(8) val vecFavGroup: List<StFavoriteGroup>? = null,
@SerialId(9) val vecTroopListExt: List<StTroopNum>? = null @JceId(9) val vecTroopListExt: List<StTroopNum>? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class StTroopNum( internal class StTroopNum(
@SerialId(0) val groupUin: Long, @JceId(0) val groupUin: Long,
@SerialId(1) val groupCode: Long, @JceId(1) val groupCode: Long,
@SerialId(2) val flag: Byte? = null, @JceId(2) val flag: Byte? = null,
@SerialId(3) val dwGroupInfoSeq: Long? = null, @JceId(3) val dwGroupInfoSeq: Long? = null,
@SerialId(4) val groupName: String = "", @JceId(4) val groupName: String = "",
@SerialId(5) val groupMemo: String = "", @JceId(5) val groupMemo: String = "",
@SerialId(6) val dwGroupFlagExt: Long? = null, @JceId(6) val dwGroupFlagExt: Long? = null,
@SerialId(7) val dwGroupRankSeq: Long? = null, @JceId(7) val dwGroupRankSeq: Long? = null,
@SerialId(8) val dwCertificationType: Long? = null, @JceId(8) val dwCertificationType: Long? = null,
@SerialId(9) val dwShutUpTimestamp: Long? = null, @JceId(9) val dwShutUpTimestamp: Long? = null,
@SerialId(10) val dwMyShutUpTimestamp: Long? = null, @JceId(10) val dwMyShutUpTimestamp: Long? = null,
@SerialId(11) val dwCmdUinUinFlag: Long? = null, @JceId(11) val dwCmdUinUinFlag: Long? = null,
@SerialId(12) val dwAdditionalFlag: Long? = null, @JceId(12) val dwAdditionalFlag: Long? = null,
@SerialId(13) val dwGroupTypeFlag: Long? = null, @JceId(13) val dwGroupTypeFlag: Long? = null,
@SerialId(14) val dwGroupSecType: Long? = null, @JceId(14) val dwGroupSecType: Long? = null,
@SerialId(15) val dwGroupSecTypeInfo: Long? = null, @JceId(15) val dwGroupSecTypeInfo: Long? = null,
@SerialId(16) val dwGroupClassExt: Long? = null, @JceId(16) val dwGroupClassExt: Long? = null,
@SerialId(17) val dwAppPrivilegeFlag: Long? = null, @JceId(17) val dwAppPrivilegeFlag: Long? = null,
@SerialId(18) val dwSubscriptionUin: Long? = null, @JceId(18) val dwSubscriptionUin: Long? = null,
@SerialId(19) val dwMemberNum: Long? = null, @JceId(19) val dwMemberNum: Long? = null,
@SerialId(20) val dwMemberNumSeq: Long? = null, @JceId(20) val dwMemberNumSeq: Long? = null,
@SerialId(21) val dwMemberCardSeq: Long? = null, @JceId(21) val dwMemberCardSeq: Long? = null,
@SerialId(22) val dwGroupFlagExt3: Long? = null, @JceId(22) val dwGroupFlagExt3: Long? = null,
@SerialId(23) val dwGroupOwnerUin: Long, @JceId(23) val dwGroupOwnerUin: Long,
@SerialId(24) val isConfGroup: Byte? = null, @JceId(24) val isConfGroup: Byte? = null,
@SerialId(25) val isModifyConfGroupFace: Byte? = null, @JceId(25) val isModifyConfGroupFace: Byte? = null,
@SerialId(26) val isModifyConfGroupName: Byte? = null, @JceId(26) val isModifyConfGroupName: Byte? = null,
@SerialId(27) val dwCmduinJoinTime: Long? = null, @JceId(27) val dwCmduinJoinTime: Long? = null,
@SerialId(28) val ulCompanyId: Long? = null, @JceId(28) val ulCompanyId: Long? = null,
@SerialId(29) val dwMaxGroupMemberNum: Long? = null, @JceId(29) val dwMaxGroupMemberNum: Long? = null,
@SerialId(30) val dwCmdUinGroupMask: Long? = null, @JceId(30) val dwCmdUinGroupMask: Long? = null,
@SerialId(31) val udwHLGuildAppid: Long? = null, @JceId(31) val udwHLGuildAppid: Long? = null,
@SerialId(32) val udwHLGuildSubType: Long? = null, @JceId(32) val udwHLGuildSubType: Long? = null,
@SerialId(33) val udwCmdUinRingtoneID: Long? = null, @JceId(33) val udwCmdUinRingtoneID: Long? = null,
@SerialId(34) val udwCmdUinFlagEx2: Long? = null @JceId(34) val udwCmdUinFlagEx2: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class StGroupRankInfo( internal class StGroupRankInfo(
@SerialId(0) val dwGroupCode: Long, @JceId(0) val dwGroupCode: Long,
@SerialId(1) val groupRankSysFlag: Byte? = null, @JceId(1) val groupRankSysFlag: Byte? = null,
@SerialId(2) val groupRankUserFlag: Byte? = null, @JceId(2) val groupRankUserFlag: Byte? = null,
@SerialId(3) val vecRankMap: List<StLevelRankPair>? = null, @JceId(3) val vecRankMap: List<StLevelRankPair>? = null,
@SerialId(4) val dwGroupRankSeq: Long? = null, @JceId(4) val dwGroupRankSeq: Long? = null,
@SerialId(5) val ownerName: String? = "", @JceId(5) val ownerName: String? = "",
@SerialId(6) val adminName: String? = "", @JceId(6) val adminName: String? = "",
@SerialId(7) val dwOfficeMode: Long? = null @JceId(7) val dwOfficeMode: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class StFavoriteGroup( internal class StFavoriteGroup(
@SerialId(0) val dwGroupCode: Long, @JceId(0) val dwGroupCode: Long,
@SerialId(1) val dwTimestamp: Long? = null, @JceId(1) val dwTimestamp: Long? = null,
@SerialId(2) val dwSnsFlag: Long? = 1L, @JceId(2) val dwSnsFlag: Long? = 1L,
@SerialId(3) val dwOpenTimestamp: Long? = null @JceId(3) val dwOpenTimestamp: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class StLevelRankPair( internal class StLevelRankPair(
@SerialId(0) val dwLevel: Long? = null, @JceId(0) val dwLevel: Long? = null,
@SerialId(1) val rank: String? = "" @JceId(1) val rank: String? = ""
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class GetTroopMemberListReq( internal class GetTroopMemberListReq(
@SerialId(0) val uin: Long, @JceId(0) val uin: Long,
@SerialId(1) val groupCode: Long, @JceId(1) val groupCode: Long,
@SerialId(2) val nextUin: Long, @JceId(2) val nextUin: Long,
@SerialId(3) val groupUin: Long, @JceId(3) val groupUin: Long,
@SerialId(4) val version: Long? = null, @JceId(4) val version: Long? = null,
@SerialId(5) val reqType: Long? = null, @JceId(5) val reqType: Long? = null,
@SerialId(6) val getListAppointTime: Long? = null, @JceId(6) val getListAppointTime: Long? = null,
@SerialId(7) val richCardNameVer: Byte? = null @JceId(7) val richCardNameVer: Byte? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class GetTroopMemberListResp( internal class GetTroopMemberListResp(
@SerialId(0) val uin: Long, @JceId(0) val uin: Long,
@SerialId(1) val groupCode: Long, @JceId(1) val groupCode: Long,
@SerialId(2) val groupUin: Long, @JceId(2) val groupUin: Long,
@SerialId(3) val vecTroopMember: List<StTroopMemberInfo>, @JceId(3) val vecTroopMember: List<StTroopMemberInfo>,
@SerialId(4) val nextUin: Long, @JceId(4) val nextUin: Long,
@SerialId(5) val result: Int, @JceId(5) val result: Int,
@SerialId(6) val errorCode: Short? = null, @JceId(6) val errorCode: Short? = null,
@SerialId(7) val officeMode: Long? = null, @JceId(7) val officeMode: Long? = null,
@SerialId(8) val nextGetTime: Long? = null @JceId(8) val nextGetTime: Long? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class StTroopMemberInfo( internal class StTroopMemberInfo(
@SerialId(0) val memberUin: Long, @JceId(0) val memberUin: Long,
@SerialId(1) val faceId: Short, @JceId(1) val faceId: Short,
@SerialId(2) val age: Byte, @JceId(2) val age: Byte,
@SerialId(3) val gender: Byte, @JceId(3) val gender: Byte,
@SerialId(4) val nick: String = "", @JceId(4) val nick: String = "",
@SerialId(5) val status: Byte = 20, @JceId(5) val status: Byte = 20,
@SerialId(6) val sShowName: String? = null, @JceId(6) val sShowName: String? = null,
@SerialId(8) val sName: String? = null, @JceId(8) val sName: String? = null,
@SerialId(9) val cGender: Byte? = null, @JceId(9) val cGender: Byte? = null,
@SerialId(10) val sPhone: String? = "", @JceId(10) val sPhone: String? = "",
@SerialId(11) val sEmail: String? = "", @JceId(11) val sEmail: String? = "",
@SerialId(12) val sMemo: String? = "", @JceId(12) val sMemo: String? = "",
@SerialId(13) val autoRemark: String? = "", @JceId(13) val autoRemark: String? = "",
@SerialId(14) val dwMemberLevel: Long? = null, @JceId(14) val dwMemberLevel: Long? = null,
@SerialId(15) val dwJoinTime: Long? = null, @JceId(15) val dwJoinTime: Long? = null,
@SerialId(16) val dwLastSpeakTime: Long? = null, @JceId(16) val dwLastSpeakTime: Long? = null,
@SerialId(17) val dwCreditLevel: Long? = null, @JceId(17) val dwCreditLevel: Long? = null,
@SerialId(18) val dwFlag: Long? = null, @JceId(18) val dwFlag: Long? = null,
@SerialId(19) val dwFlagExt: Long? = null, @JceId(19) val dwFlagExt: Long? = null,
@SerialId(20) val dwPoint: Long? = null, @JceId(20) val dwPoint: Long? = null,
@SerialId(21) val concerned: Byte? = null, @JceId(21) val concerned: Byte? = null,
@SerialId(22) val shielded: Byte? = null, @JceId(22) val shielded: Byte? = null,
@SerialId(23) val sSpecialTitle: String? = "", @JceId(23) val sSpecialTitle: String? = "",
@SerialId(24) val dwSpecialTitleExpireTime: Long? = null, @JceId(24) val dwSpecialTitleExpireTime: Long? = null,
@SerialId(25) val job: String? = "", @JceId(25) val job: String? = "",
@SerialId(26) val apolloFlag: Byte? = null, @JceId(26) val apolloFlag: Byte? = null,
@SerialId(27) val dwApolloTimestamp: Long? = null, @JceId(27) val dwApolloTimestamp: Long? = null,
@SerialId(28) val dwGlobalGroupLevel: Long? = null, @JceId(28) val dwGlobalGroupLevel: Long? = null,
@SerialId(29) val dwTitleId: Long? = null, @JceId(29) val dwTitleId: Long? = null,
@SerialId(30) val dwShutupTimestap: Long? = null, @JceId(30) val dwShutupTimestap: Long? = null,
@SerialId(31) val dwGlobalGroupPoint: Long? = null, @JceId(31) val dwGlobalGroupPoint: Long? = null,
@SerialId(32) val qzusrinfo: QzoneUserInfo? = null, @JceId(32) val qzusrinfo: QzoneUserInfo? = null,
@SerialId(33) val richCardNameVer: Byte? = null, @JceId(33) val richCardNameVer: Byte? = null,
@SerialId(34) val dwVipType: Long? = null, @JceId(34) val dwVipType: Long? = null,
@SerialId(35) val dwVipLevel: Long? = null, @JceId(35) val dwVipLevel: Long? = null,
@SerialId(36) val dwBigClubLevel: Long? = null, @JceId(36) val dwBigClubLevel: Long? = null,
@SerialId(37) val dwBigClubFlag: Long? = null, @JceId(37) val dwBigClubFlag: Long? = null,
@SerialId(38) val dwNameplate: Long? = null, @JceId(38) val dwNameplate: Long? = null,
@SerialId(39) val vecGroupHonor: ByteArray? = null @JceId(39) val vecGroupHonor: ByteArray? = null
) : JceStruct ) : JceStruct
@Serializable @Serializable
internal class QzoneUserInfo( internal class QzoneUserInfo(
@SerialId(0) val eStarState: Int? = null, @JceId(0) val eStarState: Int? = null,
@SerialId(1) val extendInfo: Map<String, String>? = null @JceId(1) val extendInfo: Map<String, String>? = null
) : JceStruct ) : JceStruct

View File

@ -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,118 +18,118 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
internal class Cmd0x352 : ProtoBuf { internal class Cmd0x352 : ProtoBuf {
@Serializable @Serializable
class DelImgReq( class DelImgReq(
@SerialId(1) val srcUin: Long = 0L, @ProtoId(1) val srcUin: Long = 0L,
@SerialId(2) val dstUin: Long = 0L, @ProtoId(2) val dstUin: Long = 0L,
@SerialId(3) val reqTerm: Int = 0, @ProtoId(3) val reqTerm: Int = 0,
@SerialId(4) val reqPlatformType: Int = 0, @ProtoId(4) val reqPlatformType: Int = 0,
@SerialId(5) val buType: Int = 0, @ProtoId(5) val buType: Int = 0,
@SerialId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val picWidth: Int = 0, @ProtoId(8) val picWidth: Int = 0,
@SerialId(9) val picHeight: Int = 0 @ProtoId(9) val picHeight: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class DelImgRsp( class DelImgRsp(
@SerialId(1) val result: Int = 0, @ProtoId(1) val result: Int = 0,
@SerialId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GetImgUrlReq( class GetImgUrlReq(
@SerialId(1) val srcUin: Long = 0L, @ProtoId(1) val srcUin: Long = 0L,
@SerialId(2) val dstUin: Long = 0L, @ProtoId(2) val dstUin: Long = 0L,
@SerialId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val urlFlag: Int = 0, @ProtoId(4) val urlFlag: Int = 0,
@SerialId(6) val urlType: Int = 0, @ProtoId(6) val urlType: Int = 0,
@SerialId(7) val reqTerm: Int = 0, @ProtoId(7) val reqTerm: Int = 0,
@SerialId(8) val reqPlatformType: Int = 0, @ProtoId(8) val reqPlatformType: Int = 0,
@SerialId(9) val srcFileType: Int = 0, @ProtoId(9) val srcFileType: Int = 0,
@SerialId(10) val innerIp: Int = 0, @ProtoId(10) val innerIp: Int = 0,
@SerialId(11) val boolAddressBook: Boolean = false, @ProtoId(11) val boolAddressBook: Boolean = false,
@SerialId(12) val buType: Int = 0, @ProtoId(12) val buType: Int = 0,
@SerialId(13) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(13) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(14) val picUpTimestamp: Int = 0, @ProtoId(14) val picUpTimestamp: Int = 0,
@SerialId(15) val reqTransferType: Int = 0 @ProtoId(15) val reqTransferType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GetImgUrlRsp( class GetImgUrlRsp(
@SerialId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val clientIp: Int = 0, @ProtoId(2) val clientIp: Int = 0,
@SerialId(3) val result: Int = 0, @ProtoId(3) val result: Int = 0,
@SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val bytesThumbDownUrl: List<ByteArray>? = null, @ProtoId(5) val bytesThumbDownUrl: List<ByteArray>? = null,
@SerialId(6) val bytesOriginalDownUrl: List<ByteArray>? = null, @ProtoId(6) val bytesOriginalDownUrl: List<ByteArray>? = null,
@SerialId(7) val msgImgInfo: ImgInfo? = null, @ProtoId(7) val msgImgInfo: ImgInfo? = null,
@SerialId(8) val uint32DownIp: List<Int>? = null, @ProtoId(8) val uint32DownIp: List<Int>? = null,
@SerialId(9) val uint32DownPort: List<Int>? = null, @ProtoId(9) val uint32DownPort: List<Int>? = null,
@SerialId(10) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(10) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(11) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(11) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(12) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(13) val bytesBigDownUrl: List<ByteArray>? = null, @ProtoId(13) val bytesBigDownUrl: List<ByteArray>? = null,
@SerialId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(15) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(15) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(16) val httpsUrlFlag: Int = 0, @ProtoId(16) val httpsUrlFlag: Int = 0,
@SerialId(26) val msgDownIp6: List<IPv6Info>? = null, @ProtoId(26) val msgDownIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")
@Serializable @Serializable
data class ImgInfo( data class ImgInfo(
@SerialId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val fileType: Int = 0, @ProtoId(2) val fileType: Int = 0,
@SerialId(3) val fileSize: Long = 0L, @ProtoId(3) val fileSize: Long = 0L,
@SerialId(4) val fileWidth: Int = 0, @ProtoId(4) val fileWidth: Int = 0,
@SerialId(5) val fileHeight: Int = 0, @ProtoId(5) val fileHeight: Int = 0,
@SerialId(6) val fileFlag: Long = 0L, @ProtoId(6) val fileFlag: Long = 0L,
@SerialId(7) val fileCutPos: Int = 0 @ProtoId(7) val fileCutPos: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class IPv6Info( class IPv6Info(
@SerialId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val port: Int = 0 @ProtoId(2) val port: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReqBody( class ReqBody(
@SerialId(1) val subcmd: Int = 0, //2是GetImgUrlReq 1是UploadImgReq @ProtoId(1) val subcmd: Int = 0, //2是GetImgUrlReq 1是UploadImgReq
@SerialId(2) val msgTryupImgReq: List<TryUpImgReq>? = null,// optional @ProtoId(2) val msgTryupImgReq: List<TryUpImgReq>? = null,// optional
@SerialId(3) val msgGetimgUrlReq: List<GetImgUrlReq>? = null,// optional @ProtoId(3) val msgGetimgUrlReq: List<GetImgUrlReq>? = null,// optional
@SerialId(4) val msgDelImgReq: List<DelImgReq>? = null, @ProtoId(4) val msgDelImgReq: List<DelImgReq>? = null,
@SerialId(10) val netType: Int = 3// 数据网络=5 @ProtoId(10) val netType: Int = 3// 数据网络=5
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RspBody( class RspBody(
@SerialId(1) val subcmd: Int = 0, @ProtoId(1) val subcmd: Int = 0,
@SerialId(2) val msgTryupImgRsp: List<TryUpImgRsp>? = null, @ProtoId(2) val msgTryupImgRsp: List<TryUpImgRsp>? = null,
@SerialId(3) val msgGetimgUrlRsp: List<GetImgUrlRsp>? = null, @ProtoId(3) val msgGetimgUrlRsp: List<GetImgUrlRsp>? = null,
@SerialId(4) val boolNewBigchan: Boolean = false, @ProtoId(4) val boolNewBigchan: Boolean = false,
@SerialId(5) val msgDelImgRsp: List<DelImgRsp>? = null, @ProtoId(5) val msgDelImgRsp: List<DelImgRsp>? = null,
@SerialId(10) val failMsg: String? = "" @ProtoId(10) val failMsg: String? = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class TryUpImgReq( internal class TryUpImgReq(
@SerialId(1) val srcUin: Int, @ProtoId(1) val srcUin: Int,
@SerialId(2) val dstUin: Int, @ProtoId(2) val dstUin: Int,
@SerialId(3) val fileId: Int = 0,//从0开始的自增数貌似有一个连接就要自增1, 但是又会重置回0 @ProtoId(3) val fileId: Int = 0,//从0开始的自增数貌似有一个连接就要自增1, 但是又会重置回0
@SerialId(4) val fileMd5: ByteArray, @ProtoId(4) val fileMd5: ByteArray,
@SerialId(5) val fileSize: Int, @ProtoId(5) val fileSize: Int,
@SerialId(6) val fileName: String,//默认为md5+".jpg" @ProtoId(6) val fileName: String,//默认为md5+".jpg"
@SerialId(7) val srcTerm: Int = 5, @ProtoId(7) val srcTerm: Int = 5,
@SerialId(8) val platformType: Int = 9, @ProtoId(8) val platformType: Int = 9,
@SerialId(9) val innerIP: Int = 0, @ProtoId(9) val innerIP: Int = 0,
@SerialId(10) val addressBook: Int = 0,//chatType == 1006为1 我觉得发0没问题 @ProtoId(10) val addressBook: Int = 0,//chatType == 1006为1 我觉得发0没问题
@SerialId(11) val retry: Int = 0,//default @ProtoId(11) val retry: Int = 0,//default
@SerialId(12) val buType: Int = 1,//1或96 不确定 @ProtoId(12) val buType: Int = 1,//1或96 不确定
@SerialId(13) val imgOriginal: Int,//是否为原图 @ProtoId(13) val imgOriginal: Int,//是否为原图
@SerialId(14) val imgWidth: Int, @ProtoId(14) val imgWidth: Int,
@SerialId(15) val imgHeight: Int, @ProtoId(15) val imgHeight: Int,
/** /**
* ImgType: * ImgType:
* JPG: 1000 * JPG: 1000
@ -140,50 +140,50 @@ internal class Cmd0x352 : ProtoBuf {
* APNG: 2001 * APNG: 2001
* SHARPP: 1004 * SHARPP: 1004
*/ */
@SerialId(16) val imgType: Int = 1000, @ProtoId(16) val imgType: Int = 1000,
@SerialId(17) val buildVer: String = "8.2.0.1296",//版本号 @ProtoId(17) val buildVer: String = "8.2.7.4410",//版本号
@SerialId(18) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY,//default @ProtoId(18) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY,//default
@SerialId(19) val fileStoreDays: Int = 0,//default @ProtoId(19) val fileStoreDays: Int = 0,//default
@SerialId(20) val stepFlag: Int = 0,//default @ProtoId(20) val stepFlag: Int = 0,//default
@SerialId(21) val rejectTryFast: Int = 0,//bool @ProtoId(21) val rejectTryFast: Int = 0,//bool
@SerialId(22) val srvUpload: Int = 1,//typeHotPic[1/2/3] @ProtoId(22) val srvUpload: Int = 1,//typeHotPic[1/2/3]
@SerialId(23) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY//rawDownloadUrl, 如果没有就是EMPTY_BYTE_ARRAY @ProtoId(23) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY//rawDownloadUrl, 如果没有就是EMPTY_BYTE_ARRAY
) : ImgReq ) : ImgReq
@Serializable @Serializable
class TryUpImgRsp( class TryUpImgRsp(
@SerialId(1) val fileId: Long = 0L, @ProtoId(1) val fileId: Long = 0L,
@SerialId(2) val clientIp: Int = 0, @ProtoId(2) val clientIp: Int = 0,
@SerialId(3) val result: Int = 0, @ProtoId(3) val result: Int = 0,
@SerialId(4) val failMsg: String? = "", @ProtoId(4) val failMsg: String? = "",
@SerialId(5) val boolFileExit: Boolean = false, @ProtoId(5) val boolFileExit: Boolean = false,
@SerialId(6) val msgImgInfo: ImgInfo? = null, @ProtoId(6) val msgImgInfo: ImgInfo? = null,
@SerialId(7) val uint32UpIp: List<Int>? = null, @ProtoId(7) val uint32UpIp: List<Int>? = null,
@SerialId(8) val uint32UpPort: List<Int>? = null, @ProtoId(8) val uint32UpPort: List<Int>? = null,
@SerialId(9) val upUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(9) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val upResid: String = "", @ProtoId(10) val upResid: String = "",
@SerialId(11) val upUuid: String = "", @ProtoId(11) val upUuid: String = "",
@SerialId(12) val upOffset: Long = 0L, @ProtoId(12) val upOffset: Long = 0L,
@SerialId(13) val blockSize: Long = 0L, @ProtoId(13) val blockSize: Long = 0L,
@SerialId(14) val encryptDstip: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(14) val encryptDstip: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(15) val roamdays: Int = 0, @ProtoId(15) val roamdays: Int = 0,
@SerialId(26) val msgUpIp6: List<IPv6Info>? = null, @ProtoId(26) val msgUpIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(60) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(60) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(61) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(61) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(62) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(62) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(64) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(64) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(65) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(65) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(66) val httpsUrlFlag: Int = 0, @ProtoId(66) val httpsUrlFlag: Int = 0,
@SerialId(1001) val msgInfo4busi: TryUpInfo4Busi? = null @ProtoId(1001) val msgInfo4busi: TryUpInfo4Busi? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class TryUpInfo4Busi( class TryUpInfo4Busi(
@SerialId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(5) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -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,141 +18,141 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
internal class Cmd0x388 : ProtoBuf { internal class Cmd0x388 : ProtoBuf {
@Serializable @Serializable
class DelImgReq( class DelImgReq(
@SerialId(1) val srcUin: Long = 0L, @ProtoId(1) val srcUin: Long = 0L,
@SerialId(2) val dstUin: Long = 0L, @ProtoId(2) val dstUin: Long = 0L,
@SerialId(3) val reqTerm: Int = 0, @ProtoId(3) val reqTerm: Int = 0,
@SerialId(4) val reqPlatformType: Int = 0, @ProtoId(4) val reqPlatformType: Int = 0,
@SerialId(5) val buType: Int = 0, @ProtoId(5) val buType: Int = 0,
@SerialId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val picWidth: Int = 0, @ProtoId(8) val picWidth: Int = 0,
@SerialId(9) val picHeight: Int = 0 @ProtoId(9) val picHeight: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class DelImgRsp( class DelImgRsp(
@SerialId(1) val result: Int = 0, @ProtoId(1) val result: Int = 0,
@SerialId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ExpRoamExtendInfo( class ExpRoamExtendInfo(
@SerialId(1) val resid: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(1) val resid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ExpRoamPicInfo( class ExpRoamPicInfo(
@SerialId(1) val shopFlag: Int = 0, @ProtoId(1) val shopFlag: Int = 0,
@SerialId(2) val pkgId: Int = 0, @ProtoId(2) val pkgId: Int = 0,
@SerialId(3) val picId: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(3) val picId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ExtensionCommPicTryUp( class ExtensionCommPicTryUp(
@SerialId(1) val bytesExtinfo: List<ByteArray>? = null @ProtoId(1) val bytesExtinfo: List<ByteArray>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ExtensionExpRoamTryUp( class ExtensionExpRoamTryUp(
@SerialId(1) val msgExproamPicInfo: List<ExpRoamPicInfo>? = null @ProtoId(1) val msgExproamPicInfo: List<ExpRoamPicInfo>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GetImgUrlReq( class GetImgUrlReq(
@SerialId(1) val groupCode: Long = 0L, @ProtoId(1) val groupCode: Long = 0L,
@SerialId(2) val dstUin: Long = 0L, @ProtoId(2) val dstUin: Long = 0L,
@SerialId(3) val fileid: Long = 0L, @ProtoId(3) val fileid: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val urlFlag: Int = 0, @ProtoId(5) val urlFlag: Int = 0,
@SerialId(6) val urlType: Int = 0, @ProtoId(6) val urlType: Int = 0,
@SerialId(7) val reqTerm: Int = 0, @ProtoId(7) val reqTerm: Int = 0,
@SerialId(8) val reqPlatformType: Int = 0, @ProtoId(8) val reqPlatformType: Int = 0,
@SerialId(9) val innerIp: Int = 0, @ProtoId(9) val innerIp: Int = 0,
@SerialId(10) val buType: Int = 0, @ProtoId(10) val buType: Int = 0,
@SerialId(11) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(11) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val fileId: Long = 0L, @ProtoId(12) val fileId: Long = 0L,
@SerialId(13) val fileSize: Long = 0L, @ProtoId(13) val fileSize: Long = 0L,
@SerialId(14) val originalPic: Int = 0, @ProtoId(14) val originalPic: Int = 0,
@SerialId(15) val retryReq: Int = 0, @ProtoId(15) val retryReq: Int = 0,
@SerialId(16) val fileHeight: Int = 0, @ProtoId(16) val fileHeight: Int = 0,
@SerialId(17) val fileWidth: Int = 0, @ProtoId(17) val fileWidth: Int = 0,
@SerialId(18) val picType: Int = 0, @ProtoId(18) val picType: Int = 0,
@SerialId(19) val picUpTimestamp: Int = 0, @ProtoId(19) val picUpTimestamp: Int = 0,
@SerialId(20) val reqTransferType: Int = 0 @ProtoId(20) val reqTransferType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GetImgUrlRsp( class GetImgUrlRsp(
@SerialId(1) val fileid: Long = 0L, @ProtoId(1) val fileid: Long = 0L,
@SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val result: Int = 0, @ProtoId(3) val result: Int = 0,
@SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val msgImgInfo: ImgInfo? = null, @ProtoId(5) val msgImgInfo: ImgInfo? = null,
@SerialId(6) val bytesThumbDownUrl: List<ByteArray>? = null, @ProtoId(6) val bytesThumbDownUrl: List<ByteArray>? = null,
@SerialId(7) val bytesOriginalDownUrl: List<ByteArray>? = null, @ProtoId(7) val bytesOriginalDownUrl: List<ByteArray>? = null,
@SerialId(8) val bytesBigDownUrl: List<ByteArray>? = null, @ProtoId(8) val bytesBigDownUrl: List<ByteArray>? = null,
@SerialId(9) val uint32DownIp: List<Int>? = null, @ProtoId(9) val uint32DownIp: List<Int>? = null,
@SerialId(10) val uint32DownPort: List<Int>? = null, @ProtoId(10) val uint32DownPort: List<Int>? = null,
@SerialId(11) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(11) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(12) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(13) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(13) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(15) val fileId: Long = 0L, @ProtoId(15) val fileId: Long = 0L,
@SerialId(16) val autoDownType: Int = 0, @ProtoId(16) val autoDownType: Int = 0,
@SerialId(17) val uint32OrderDownType: List<Int>? = null, @ProtoId(17) val uint32OrderDownType: List<Int>? = null,
@SerialId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(20) val httpsUrlFlag: Int = 0, @ProtoId(20) val httpsUrlFlag: Int = 0,
@SerialId(26) val msgDownIp6: List<IPv6Info>? = null, @ProtoId(26) val msgDownIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GetPttUrlReq( class GetPttUrlReq(
@SerialId(1) val groupCode: Long = 0L, @ProtoId(1) val groupCode: Long = 0L,
@SerialId(2) val dstUin: Long = 0L, @ProtoId(2) val dstUin: Long = 0L,
@SerialId(3) val fileid: Long = 0L, @ProtoId(3) val fileid: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val reqTerm: Int = 0, @ProtoId(5) val reqTerm: Int = 0,
@SerialId(6) val reqPlatformType: Int = 0, @ProtoId(6) val reqPlatformType: Int = 0,
@SerialId(7) val innerIp: Int = 0, @ProtoId(7) val innerIp: Int = 0,
@SerialId(8) val buType: Int = 0, @ProtoId(8) val buType: Int = 0,
@SerialId(9) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(9) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val fileId: Long = 0L, @ProtoId(10) val fileId: Long = 0L,
@SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val codec: Int = 0, @ProtoId(12) val codec: Int = 0,
@SerialId(13) val buId: Int = 0, @ProtoId(13) val buId: Int = 0,
@SerialId(14) val reqTransferType: Int = 0, @ProtoId(14) val reqTransferType: Int = 0,
@SerialId(15) val isAuto: Int = 0 @ProtoId(15) val isAuto: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GetPttUrlRsp( class GetPttUrlRsp(
@SerialId(1) val fileid: Long = 0L, @ProtoId(1) val fileid: Long = 0L,
@SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val result: Int = 0, @ProtoId(3) val result: Int = 0,
@SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val bytesDownUrl: List<ByteArray>? = null, @ProtoId(5) val bytesDownUrl: List<ByteArray>? = null,
@SerialId(6) val uint32DownIp: List<Int>? = null, @ProtoId(6) val uint32DownIp: List<Int>? = null,
@SerialId(7) val uint32DownPort: List<Int>? = null, @ProtoId(7) val uint32DownPort: List<Int>? = null,
@SerialId(8) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(8) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val downPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(9) val downPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val fileId: Long = 0L, @ProtoId(10) val fileId: Long = 0L,
@SerialId(11) val transferType: Int = 0, @ProtoId(11) val transferType: Int = 0,
@SerialId(12) val allowRetry: Int = 0, @ProtoId(12) val allowRetry: Int = 0,
@SerialId(26) val msgDownIp6: List<IPv6Info>? = null, @ProtoId(26) val msgDownIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(28) val strDomain: String = "" @ProtoId(28) val strDomain: String = ""
) : ProtoBuf ) : ProtoBuf
@Suppress("ArrayInDataClass") @Suppress("ArrayInDataClass")
@Serializable @Serializable
data class ImgInfo( data class ImgInfo(
@SerialId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val fileType: Int = 0, @ProtoId(2) val fileType: Int = 0,
@SerialId(3) val fileSize: Long = 0L, @ProtoId(3) val fileSize: Long = 0L,
@SerialId(4) val fileWidth: Int = 0, @ProtoId(4) val fileWidth: Int = 0,
@SerialId(5) val fileHeight: Int = 0 @ProtoId(5) val fileHeight: Int = 0
) : ProtoBuf { ) : ProtoBuf {
override fun toString(): String { override fun toString(): String {
return "ImgInfo(fileMd5=${fileMd5.contentToString()}, fileType=$fileType, fileSize=$fileSize, fileWidth=$fileWidth, fileHeight=$fileHeight)" return "ImgInfo(fileMd5=${fileMd5.contentToString()}, fileType=$fileType, fileSize=$fileSize, fileWidth=$fileWidth, fileHeight=$fileHeight)"
@ -161,128 +161,128 @@ internal class Cmd0x388 : ProtoBuf {
@Serializable @Serializable
class IPv6Info( class IPv6Info(
@SerialId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val port: Int = 0 @ProtoId(2) val port: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PicSize( class PicSize(
@SerialId(1) val original: Int = 0, @ProtoId(1) val original: Int = 0,
@SerialId(2) val thumb: Int = 0, @ProtoId(2) val thumb: Int = 0,
@SerialId(3) val high: Int = 0 @ProtoId(3) val high: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReqBody( class ReqBody(
@SerialId(1) val netType: Int = 0, @ProtoId(1) val netType: Int = 0,
@SerialId(2) val subcmd: Int = 0, @ProtoId(2) val subcmd: Int = 0,
@SerialId(3) val msgTryupImgReq: List<TryUpImgReq>? = null, @ProtoId(3) val msgTryupImgReq: List<TryUpImgReq>? = null,
@SerialId(4) val msgGetimgUrlReq: List<GetImgUrlReq>? = null, @ProtoId(4) val msgGetimgUrlReq: List<GetImgUrlReq>? = null,
@SerialId(5) val msgTryupPttReq: List<TryUpPttReq>? = null, @ProtoId(5) val msgTryupPttReq: List<TryUpPttReq>? = null,
@SerialId(6) val msgGetpttUrlReq: List<GetPttUrlReq>? = null, @ProtoId(6) val msgGetpttUrlReq: List<GetPttUrlReq>? = null,
@SerialId(7) val commandId: Int = 0, @ProtoId(7) val commandId: Int = 0,
@SerialId(8) val msgDelImgReq: List<DelImgReq>? = null, @ProtoId(8) val msgDelImgReq: List<DelImgReq>? = null,
@SerialId(1001) val extension: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(1001) val extension: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RspBody( class RspBody(
@SerialId(1) val clientIp: Int = 0, @ProtoId(1) val clientIp: Int = 0,
@SerialId(2) val subcmd: Int = 0, @ProtoId(2) val subcmd: Int = 0,
@SerialId(3) val msgTryupImgRsp: List<TryUpImgRsp>? = null, @ProtoId(3) val msgTryupImgRsp: List<TryUpImgRsp>? = null,
@SerialId(4) val msgGetimgUrlRsp: List<GetImgUrlRsp>? = null, @ProtoId(4) val msgGetimgUrlRsp: List<GetImgUrlRsp>? = null,
@SerialId(5) val msgTryupPttRsp: List<TryUpPttRsp>? = null, @ProtoId(5) val msgTryupPttRsp: List<TryUpPttRsp>? = null,
@SerialId(6) val msgGetpttUrlRsp: List<GetPttUrlRsp>? = null, @ProtoId(6) val msgGetpttUrlRsp: List<GetPttUrlRsp>? = null,
@SerialId(7) val msgDelImgRsp: List<DelImgRsp>? = null @ProtoId(7) val msgDelImgRsp: List<DelImgRsp>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class TryUpImgReq( class TryUpImgReq(
@SerialId(1) val groupCode: Long = 0L, @ProtoId(1) val groupCode: Long = 0L,
@SerialId(2) val srcUin: Long = 0L, @ProtoId(2) val srcUin: Long = 0L,
@SerialId(3) val fileId: Long = 0L, @ProtoId(3) val fileId: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileSize: Long = 0L, @ProtoId(5) val fileSize: Long = 0L,
@SerialId(6) val fileName: String ="", @ProtoId(6) val fileName: String = "",
@SerialId(7) val srcTerm: Int = 0, @ProtoId(7) val srcTerm: Int = 0,
@SerialId(8) val platformType: Int = 0, @ProtoId(8) val platformType: Int = 0,
@SerialId(9) val buType: Int = 0, @ProtoId(9) val buType: Int = 0,
@SerialId(10) val picWidth: Int = 0, @ProtoId(10) val picWidth: Int = 0,
@SerialId(11) val picHeight: Int = 0, @ProtoId(11) val picHeight: Int = 0,
@SerialId(12) val picType: Int = 0, @ProtoId(12) val picType: Int = 0,
@SerialId(13) val buildVer: String = "", @ProtoId(13) val buildVer: String = "",
@SerialId(14) val innerIp: Int = 0, @ProtoId(14) val innerIp: Int = 0,
@SerialId(15) val appPicType: Int = 0, @ProtoId(15) val appPicType: Int = 0,
@SerialId(16) val originalPic: Int = 0, @ProtoId(16) val originalPic: Int = 0,
@SerialId(17) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(17) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(18) val dstUin: Long = 0L, @ProtoId(18) val dstUin: Long = 0L,
@SerialId(19) val srvUpload: Int = 0, @ProtoId(19) val srvUpload: Int = 0,
@SerialId(20) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(20) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ImgReq ) : ImgReq
@Serializable @Serializable
class TryUpImgRsp( class TryUpImgRsp(
@SerialId(1) val fileId: Long = 0L, @ProtoId(1) val fileId: Long = 0L,
@SerialId(2) val result: Int = 0, @ProtoId(2) val result: Int = 0,
@SerialId(3) val failMsg: String = "", @ProtoId(3) val failMsg: String = "",
@SerialId(4) val boolFileExit: Boolean = false, @ProtoId(4) val boolFileExit: Boolean = false,
@SerialId(5) val msgImgInfo: ImgInfo? = null, @ProtoId(5) val msgImgInfo: ImgInfo? = null,
@SerialId(6) val uint32UpIp: List<Int>? = null, @ProtoId(6) val uint32UpIp: List<Int>? = null,
@SerialId(7) val uint32UpPort: List<Int>? = null, @ProtoId(7) val uint32UpPort: List<Int>? = null,
@SerialId(8) val upUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(8) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val fileid: Long = 0L, @ProtoId(9) val fileid: Long = 0L,
@SerialId(10) val upOffset: Long = 0L, @ProtoId(10) val upOffset: Long = 0L,
@SerialId(11) val blockSize: Long = 0L, @ProtoId(11) val blockSize: Long = 0L,
@SerialId(12) val boolNewBigChan: Boolean = false, @ProtoId(12) val boolNewBigChan: Boolean = false,
@SerialId(26) val msgUpIp6: List<IPv6Info>? = null, @ProtoId(26) val msgUpIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(1001) val msgInfo4busi: TryUpInfo4Busi? = null @ProtoId(1001) val msgInfo4busi: TryUpInfo4Busi? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class TryUpInfo4Busi( class TryUpInfo4Busi(
@SerialId(1) val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileResid: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(5) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class TryUpPttReq( class TryUpPttReq(
@SerialId(1) val groupCode: Long = 0L, @ProtoId(1) val groupCode: Long = 0L,
@SerialId(2) val srcUin: Long = 0L, @ProtoId(2) val srcUin: Long = 0L,
@SerialId(3) val fileId: Long = 0L, @ProtoId(3) val fileId: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileSize: Long = 0L, @ProtoId(5) val fileSize: Long = 0L,
@SerialId(6) val fileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(6) val fileName: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val srcTerm: Int = 0, @ProtoId(7) val srcTerm: Int = 0,
@SerialId(8) val platformType: Int = 0, @ProtoId(8) val platformType: Int = 0,
@SerialId(9) val buType: Int = 0, @ProtoId(9) val buType: Int = 0,
@SerialId(10) val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(10) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(11) val innerIp: Int = 0, @ProtoId(11) val innerIp: Int = 0,
@SerialId(12) val voiceLength: Int = 0, @ProtoId(12) val voiceLength: Int = 0,
@SerialId(13) val boolNewUpChan: Boolean = false, @ProtoId(13) val boolNewUpChan: Boolean = false,
@SerialId(14) val codec: Int = 0, @ProtoId(14) val codec: Int = 0,
@SerialId(15) val voiceType: Int = 0, @ProtoId(15) val voiceType: Int = 0,
@SerialId(16) val buId: Int = 0 @ProtoId(16) val buId: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class TryUpPttRsp( class TryUpPttRsp(
@SerialId(1) val fileId: Long = 0L, @ProtoId(1) val fileId: Long = 0L,
@SerialId(2) val result: Int = 0, @ProtoId(2) val result: Int = 0,
@SerialId(3) val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val boolFileExit: Boolean = false, @ProtoId(4) val boolFileExit: Boolean = false,
@SerialId(5) val uint32UpIp: List<Int>? = null, @ProtoId(5) val uint32UpIp: List<Int>? = null,
@SerialId(6) val uint32UpPort: List<Int>? = null, @ProtoId(6) val uint32UpPort: List<Int>? = null,
@SerialId(7) val upUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(7) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val fileid: Long = 0L, @ProtoId(8) val fileid: Long = 0L,
@SerialId(9) val upOffset: Long = 0L, @ProtoId(9) val upOffset: Long = 0L,
@SerialId(10) val blockSize: Long = 0L, @ProtoId(10) val blockSize: Long = 0L,
@SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val channelType: Int = 0, @ProtoId(12) val channelType: Int = 0,
@SerialId(26) val msgUpIp6: List<IPv6Info>? = null, @ProtoId(26) val msgUpIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -0,0 +1,408 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("SpellCheckingInspection")
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
class GroupOpenSysMsg : ProtoBuf {
@Serializable
class LightApp(
@ProtoId(1) val app: String = "",
@ProtoId(2) val view: String = "",
@ProtoId(3) val desc: String = "",
@ProtoId(4) val prompt: String = "",
@ProtoId(5) val ver: String = "",
@ProtoId(6) val meta: String = "",
@ProtoId(7) val config: String = "",
@ProtoId(8) val source: Source? = null
) : ProtoBuf
@Serializable
class RichMsg(
@ProtoId(1) val title: String = "",
@ProtoId(2) val desc: String = "",
@ProtoId(3) val brief: String = "",
@ProtoId(4) val cover: String = "",
@ProtoId(5) val url: String = "",
@ProtoId(6) val source: Source? = null
) : ProtoBuf
@Serializable
class Sender(
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val nick: String = "",
@ProtoId(3) val avatar: String = "",
@ProtoId(4) val url: String = ""
) : ProtoBuf
@Serializable
class Source(
@ProtoId(1) val name: String = "",
@ProtoId(2) val icon: String = "",
@ProtoId(3) val url: String = ""
) : ProtoBuf
@Serializable
class SysMsgBody(
@ProtoId(1) val groupId: Long = 0L,
@ProtoId(2) val appid: Long = 0L,
@ProtoId(3) val sender: Sender? = null,
@ProtoId(4) val msgType: Int = 0,
@ProtoId(5) val content: String = "",
@ProtoId(6) val richMsg: RichMsg? = null,
@ProtoId(7) val lightApp: LightApp? = null
) : ProtoBuf
}
@Serializable
class TroopTips0x857 : ProtoBuf {
@Serializable
class AIOGrayTipsInfo(
@ProtoId(1) val optUint32ShowLastest: Int = 0,
@ProtoId(2) val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val optUint32Remind: Int = 0,
@ProtoId(4) val optBytesBrief: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val receiverUin: Long = 0L,
@ProtoId(6) val reliaoAdminOpt: Int = 0,
@ProtoId(7) val robotGroupOpt: Int = 0
) : ProtoBuf
@Serializable
class AIOTopTipsInfo(
@ProtoId(1) val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val optUint32Icon: Int = 0,
@ProtoId(3) val optEnumAction: Int /* enum */ = 1,
@ProtoId(4) val optBytesUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val optBytesData: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val optBytesDataI: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(7) val optBytesDataA: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(8) val optBytesDataP: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class FloatedTipsInfo(
@ProtoId(1) val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class GeneralGrayTipInfo(
@ProtoId(1) val busiType: Long = 0L,
@ProtoId(2) val busiId: Long = 0L,
@ProtoId(3) val ctrlFlag: Int = 0,
@ProtoId(4) val c2cType: Int = 0,
@ProtoId(5) val serviceType: Int = 0,
@ProtoId(6) val templId: Long = 0L,
@ProtoId(7) val msgTemplParam: List<TemplParam>? = null,
@ProtoId(8) val content: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(10) val tipsSeqId: Long = 0L,
@ProtoId(100) val pbReserv: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class GoldMsgTipsElem(
@ProtoId(1) val type: Int = 0,
@ProtoId(2) val billno: String = "",
@ProtoId(3) val result: Int = 0,
@ProtoId(4) val amount: Int = 0,
@ProtoId(5) val total: Int = 0,
@ProtoId(6) val interval: Int = 0,
@ProtoId(7) val finish: Int = 0,
@ProtoId(8) val uin: List<Long>? = null,
@ProtoId(9) val action: Int = 0
) : ProtoBuf
@Serializable
class GroupInfoChange(
@ProtoId(1) val groupHonorSwitch: Int = 0
) : ProtoBuf
@Serializable
class GroupNotifyInfo(
@ProtoId(1) val optUint32AutoPullFlag: Int = 0,
@ProtoId(2) val optBytesFeedsId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class InstCtrl(
@ProtoId(1) val msgSendToInst: List<InstInfo>? = null,
@ProtoId(2) val msgExcludeInst: List<InstInfo>? = null,
@ProtoId(3) val msgFromInst: InstInfo? = null
) : ProtoBuf
@Serializable
class InstInfo(
@ProtoId(1) val apppid: Int = 0,
@ProtoId(2) val instid: Int = 0,
@ProtoId(3) val platform: Int = 0,
@ProtoId(4) val openAppid: Int = 0,
@ProtoId(5) val productid: Int = 0,
@ProtoId(6) val ssoBid: Int = 0,
@ProtoId(7) val guid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(8) val verMin: Int = 0,
@ProtoId(9) val verMax: Int = 0
) : ProtoBuf
@Serializable
class LbsShareChangePushInfo(
@ProtoId(1) val msgType: Int = 0,
@ProtoId(2) val msgInfo: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val groupId: Long = 0L,
@ProtoId(5) val operUin: Long = 0L,
@ProtoId(6) val grayTips: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(7) val msgSeq: Long = 0L,
@ProtoId(8) val joinNums: Int = 0,
@ProtoId(99) val pushType: Int = 0,
@ProtoId(100) val extInfo: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class LuckyBagNotify(
@ProtoId(1) val msgTips: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class MediaChangePushInfo(
@ProtoId(1) val msgType: Int = 0,
@ProtoId(2) val msgInfo: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val groupId: Long = 0L,
@ProtoId(5) val operUin: Long = 0L,
@ProtoId(6) val grayTips: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(7) val msgSeq: Long = 0L,
@ProtoId(8) val joinNums: Int = 0,
@ProtoId(9) val msgPerSetting: PersonalSetting? = null,
@ProtoId(10) val playMode: Int = 0,
@ProtoId(99) val mediaType: Int = 0,
@ProtoId(100) val extInfo: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf {
@Serializable
class PersonalSetting(
@ProtoId(1) val themeId: Int = 0,
@ProtoId(2) val playerId: Int = 0,
@ProtoId(3) val fontId: Int = 0
) : ProtoBuf
}
@Serializable
class MessageBoxInfo(
@ProtoId(1) val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val optBytesTitle: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val optBytesButton: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class MessageRecallReminder(
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val recalledMsgList: List<MessageMeta> = listOf(),
@ProtoId(4) val reminderContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val groupType: Int = 0,
@ProtoId(7) val opType: Int = 0
) : ProtoBuf {
@Serializable
class MessageMeta(
@ProtoId(1) val seq: Int = 0,
@ProtoId(2) val time: Int = 0,
@ProtoId(3) val msgRandom: Int = 0,
@ProtoId(4) val msgType: Int = 0,
@ProtoId(5) val msgFlag: Int = 0,
@ProtoId(6) val authorUin: Long = 0L
) : ProtoBuf
}
@Serializable
class MiniAppNotify(
@ProtoId(1) val msg: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class NotifyMsgBody(
@ProtoId(1) val optEnumType: Int /* enum */ = 1,
@ProtoId(2) val optUint64MsgTime: Long = 0L,
@ProtoId(3) val optUint64MsgExpires: Long = 0L,
@ProtoId(4) val optUint64GroupCode: Long = 0L,
@ProtoId(5) val optMsgGraytips: AIOGrayTipsInfo? = null,
@ProtoId(6) val optMsgMessagebox: MessageBoxInfo? = null,
@ProtoId(7) val optMsgFloatedtips: FloatedTipsInfo? = null,
@ProtoId(8) val optMsgToptips: AIOTopTipsInfo? = null,
@ProtoId(9) val optMsgRedtips: RedGrayTipsInfo? = null,
@ProtoId(10) val optMsgGroupNotify: GroupNotifyInfo? = null,
@ProtoId(11) val optMsgRecall: MessageRecallReminder? = null,
@ProtoId(12) val optMsgThemeNotify: ThemeStateNotify? = null,
@ProtoId(13) val serviceType: Int = 0,
@ProtoId(14) val optMsgObjmsgUpdate: NotifyObjmsgUpdate? = null,
@ProtoId(15) val optMsgWerewolfPush: WereWolfPush? = null,
// @SerialId(16) val optStcmGameState: ApolloGameStatus.STCMGameMessage? = null,
// @SerialId(17) val aplloMsgPush: ApolloPushMsgInfo.STPushMsgElem? = null,
@ProtoId(18) val optMsgGoldtips: GoldMsgTipsElem? = null,
@ProtoId(20) val optMsgMiniappNotify: MiniAppNotify? = null,
@ProtoId(21) val optUint64SenderUin: Long = 0L,
@ProtoId(22) val optMsgLuckybagNotify: LuckyBagNotify? = null,
@ProtoId(23) val optMsgTroopformtipsPush: TroopFormGrayTipsInfo? = null,
@ProtoId(24) val optMsgMediaPush: MediaChangePushInfo? = null,
@ProtoId(26) val optGeneralGrayTip: GeneralGrayTipInfo? = null,
@ProtoId(27) val optMsgVideoPush: VideoChangePushInfo? = null,
@ProtoId(28) val optLbsShareChangePlusInfo: LbsShareChangePushInfo? = null,
@ProtoId(29) val optMsgSingPush: SingChangePushInfo? = null,
@ProtoId(30) val optMsgGroupInfoChange: GroupInfoChange? = null
) : ProtoBuf
@Serializable
class NotifyObjmsgUpdate(
@ProtoId(1) val objmsgId: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val updateType: Int = 0,
@ProtoId(3) val extMsg: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class RedGrayTipsInfo(
@ProtoId(1) val optUint32ShowLastest: Int = 0,
@ProtoId(2) val senderUin: Long = 0L,
@ProtoId(3) val receiverUin: Long = 0L,
@ProtoId(4) val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val authkey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoType(ProtoNumberType.SIGNED) @ProtoId(7) val sint32Msgtype: Int = 0,
@ProtoId(8) val luckyFlag: Int = 0,
@ProtoId(9) val hideFlag: Int = 0,
@ProtoId(10) val pcBody: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(11) val icon: Int = 0,
@ProtoId(12) val luckyUin: Long = 0L,
@ProtoId(13) val time: Int = 0,
@ProtoId(14) val random: Int = 0,
@ProtoId(15) val broadcastRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(16) val idiom: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(17) val idiomSeq: Int = 0,
@ProtoId(18) val idiomAlpha: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(19) val jumpurl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ReqBody(
@ProtoId(1) val optUint64GroupCode: Long = 0L,
@ProtoId(2) val uint64Memberuins: List<Long>? = null,
@ProtoId(3) val optUint32Offline: Int = 0,
@ProtoId(4) val msgInstCtrl: InstCtrl? = null,
@ProtoId(5) val optBytesMsg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val optUint32BusiType: Int = 0
) : ProtoBuf
@Serializable
class RspBody(
@ProtoId(1) val optUint64GroupCode: Long = 0L
) : ProtoBuf
@Serializable
class SingChangePushInfo(
@ProtoId(1) val seq: Long = 0L,
@ProtoId(2) val actionType: Int = 0,
@ProtoId(3) val groupId: Long = 0L,
@ProtoId(4) val operUin: Long = 0L,
@ProtoId(5) val grayTips: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val joinNums: Int = 0
) : ProtoBuf
@Serializable
class TemplParam(
@ProtoId(1) val name: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val value: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ThemeStateNotify(
@ProtoId(1) val state: Int = 0,
@ProtoId(2) val feedsId: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val themeName: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val actionUin: Long = 0L,
@ProtoId(5) val createUin: Long = 0L
) : ProtoBuf
@Serializable
class TroopFormGrayTipsInfo(
@ProtoId(1) val writerUin: Long = 0L,
@ProtoId(2) val creatorUin: Long = 0L,
@ProtoId(3) val richContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val optBytesUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val creatorNick: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class VideoChangePushInfo(
@ProtoId(1) val seq: Long = 0L,
@ProtoId(2) val actionType: Int = 0,
@ProtoId(3) val groupId: Long = 0L,
@ProtoId(4) val operUin: Long = 0L,
@ProtoId(5) val grayTips: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val joinNums: Int = 0,
@ProtoId(100) val extInfo: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class WereWolfPush(
@ProtoId(1) val pushType: Int = 0,
@ProtoId(2) val gameRoom: Long = 0L,
@ProtoId(3) val enumGameState: Int = 0,
@ProtoId(4) val gameRound: Int = 0,
@ProtoId(5) val roles: List<Role>? = null,
@ProtoId(6) val speaker: Long = 0L,
@ProtoId(7) val judgeUin: Long = 0L,
@ProtoId(8) val judgeWords: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(9) val enumOperation: Int = 0,
@ProtoId(10) val srcUser: Long = 0L,
@ProtoId(11) val dstUser: Long = 0L,
@ProtoId(12) val deadUsers: List<Long>? = null,
@ProtoId(13) val gameResult: Int = 0,
@ProtoId(14) val timeoutSec: Int = 0,
@ProtoId(15) val killConfirmed: Int = 0,
@ProtoId(16) val judgeNickname: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(17) val votedTieUsers: List<Long>? = null
) : ProtoBuf {
@Serializable
class GameRecord(
@ProtoId(1) val total: Int = 0,
@ProtoId(2) val win: Int = 0,
@ProtoId(3) val lose: Int = 0,
@ProtoId(4) val draw: Int = 0
) : ProtoBuf
@Serializable
class Role(
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val enumType: Int = 0,
@ProtoId(3) val enumState: Int = 0,
@ProtoId(4) val canSpeak: Int = 0,
@ProtoId(5) val canListen: Int = 0,
@ProtoId(6) val position: Int = 0,
@ProtoId(7) val canVote: Int = 0,
@ProtoId(8) val canVoted: Int = 0,
@ProtoId(9) val alreadyChecked: Int = 0,
@ProtoId(10) val alreadySaved: Int = 0,
@ProtoId(11) val alreadyPoisoned: Int = 0,
@ProtoId(12) val playerState: Int = 0,
@ProtoId(13) val enumDeadOp: Int = 0,
@ProtoId(14) val enumOperation: Int = 0,
@ProtoId(15) val dstUser: Long = 0L,
@ProtoId(16) val operationRound: Int = 0,
@ProtoId(17) val msgGameRecord: GameRecord? = null,
@ProtoId(18) val isWerewolf: Int = 0,
@ProtoId(19) val defendedUser: Long = 0L,
@ProtoId(20) val isSheriff: Int = 0
) : ProtoBuf
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("SpellCheckingInspection")
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import kotlinx.serialization.protobuf.ProtoNumberType
import kotlinx.serialization.protobuf.ProtoType
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
@Serializable
class Oidb0x858 : ProtoBuf {
@Serializable
class GoldMsgTipsElem(
@ProtoId(1) val type: Int = 0,
@ProtoId(2) val billno: String = "",
@ProtoId(3) val result: Int = 0,
@ProtoId(4) val amount: Int = 0,
@ProtoId(5) val total: Int = 0,
@ProtoId(6) val interval: Int = 0,
@ProtoId(7) val finish: Int = 0,
@ProtoId(8) val uin: List<Long>? = null,
@ProtoId(9) val action: Int = 0
) : ProtoBuf
@Serializable
class MessageRecallReminder(
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val recalledMsgList: List<MessageMeta> = listOf(),
@ProtoId(4) val reminderContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf {
@Serializable
class MessageMeta(
@ProtoId(1) val seq: Int = 0,
@ProtoId(2) val time: Int = 0,
@ProtoId(3) val msgRandom: Int = 0
) : ProtoBuf
}
@Serializable
class NotifyMsgBody(
@ProtoId(1) val optEnumType: Int /* enum */ = 5,
@ProtoId(2) val optUint64MsgTime: Long = 0L,
@ProtoId(3) val optUint64MsgExpires: Long = 0L,
@ProtoId(4) val optUint64ConfUin: Long = 0L,
@ProtoId(5) val optMsgRedtips: RedGrayTipsInfo? = null,
@ProtoId(6) val optMsgRecallReminder: MessageRecallReminder? = null,
@ProtoId(7) val optMsgObjUpdate: NotifyObjmsgUpdate? = null,
// @SerialId(8) val optStcmGameState: ApolloGameStatus.STCMGameMessage? = null,
// @SerialId(9) val aplloMsgPush: ApolloPushMsgInfo.STPushMsgElem? = null,
@ProtoId(10) val optMsgGoldtips: GoldMsgTipsElem? = null
) : ProtoBuf
@Serializable
class NotifyObjmsgUpdate(
@ProtoId(1) val objmsgId: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val updateType: Int = 0,
@ProtoId(3) val extMsg: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class RedGrayTipsInfo(
@ProtoId(1) val optUint32ShowLastest: Int = 0,
@ProtoId(2) val senderUin: Long = 0L,
@ProtoId(3) val receiverUin: Long = 0L,
@ProtoId(4) val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val authkey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoType(ProtoNumberType.SIGNED) @ProtoId(7) val sint32Msgtype: Int = 0,
@ProtoId(8) val luckyFlag: Int = 0,
@ProtoId(9) val hideFlag: Int = 0,
@ProtoId(10) val pcBody: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(11) val icon: Int = 0,
@ProtoId(12) val luckyUin: Long = 0L,
@ProtoId(13) val time: Int = 0,
@ProtoId(14) val random: Int = 0,
@ProtoId(15) val broadcastRichContent: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(16) val idiom: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(17) val idiomSeq: Int = 0,
@ProtoId(18) val idiomAlpha: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(19) val jumpurl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
}

View File

@ -1,69 +0,0 @@
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.ProtoBuf
@Serializable
class Common : ProtoBuf {
@Serializable
class BindInfo(
@SerialId(1) val friUin: Long = 0L,
@SerialId(2) val friNick: String = "",
@SerialId(3) val time: Long = 0L,
@SerialId(4) val bindStatus: Int = 0
) : ProtoBuf
@Serializable
class MedalInfo(
@SerialId(1) val id: Int = 0,
@SerialId(2) val type: Int = 0,
@SerialId(4) val seq: Long = 0,
@SerialId(5) val name: String = "",
@SerialId(6) val newflag: Int = 0,
@SerialId(7) val time: Long = 0L,
@SerialId(8) val msgBindFri: Common.BindInfo? = null,
@SerialId(11) val desc: String = "",
@SerialId(31) val level: Int = 0,
@SerialId(36) val taskinfos: List<Common.MedalTaskInfo>? = null,
@SerialId(40) val point: Int = 0,
@SerialId(41) val pointLevel2: Int = 0,
@SerialId(42) val pointLevel3: Int = 0,
@SerialId(43) val seqLevel2: Long = 0,
@SerialId(44) val seqLevel3: Long = 0,
@SerialId(45) val timeLevel2: Long = 0L,
@SerialId(46) val timeLevel3: Long = 0L,
@SerialId(47) val descLevel2: String = "",
@SerialId(48) val descLevel3: String = "",
@SerialId(49) val endtime: Int = 0,
@SerialId(50) val detailUrl: String = "",
@SerialId(51) val detailUrl2: String = "",
@SerialId(52) val detailUrl3: String = "",
@SerialId(53) val taskDesc: String = "",
@SerialId(54) val taskDesc2: String = "",
@SerialId(55) val taskDesc3: String = "",
@SerialId(56) val levelCount: Int = 0,
@SerialId(57) val noProgress: Int = 0,
@SerialId(58) val resource: String = "",
@SerialId(59) val fromuinLevel: Int = 0,
@SerialId(60) val unread: Int = 0,
@SerialId(61) val unread2: Int = 0,
@SerialId(62) val unread3: Int = 0
) : ProtoBuf
@Serializable
class MedalTaskInfo(
@SerialId(1) val taskid: Int = 0,
@SerialId(32) val int32TaskValue: Int = 0,
@SerialId(33) val tarValue: Int = 0,
@SerialId(34) val tarValueLevel2: Int = 0,
@SerialId(35) val tarValueLevel3: Int = 0
) : ProtoBuf
}

View File

@ -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,56 +18,56 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
class Common : ProtoBuf { class Common : ProtoBuf {
@Serializable @Serializable
class BindInfo( class BindInfo(
@SerialId(1) val friUin: Long = 0L, @ProtoId(1) val friUin: Long = 0L,
@SerialId(2) val friNick: String = "", @ProtoId(2) val friNick: String = "",
@SerialId(3) val time: Long = 0L, @ProtoId(3) val time: Long = 0L,
@SerialId(4) val bindStatus: Int = 0 @ProtoId(4) val bindStatus: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class MedalInfo( class MedalInfo(
@SerialId(1) val id: Int = 0, @ProtoId(1) val id: Int = 0,
@SerialId(2) val type: Int = 0, @ProtoId(2) val type: Int = 0,
@SerialId(4) val seq: Long = 0, @ProtoId(4) val seq: Long = 0,
@SerialId(5) val name: String = "", @ProtoId(5) val name: String = "",
@SerialId(6) val newflag: Int = 0, @ProtoId(6) val newflag: Int = 0,
@SerialId(7) val time: Long = 0L, @ProtoId(7) val time: Long = 0L,
@SerialId(8) val msgBindFri: Common.BindInfo? = null, @ProtoId(8) val msgBindFri: Common.BindInfo? = null,
@SerialId(11) val desc: String = "", @ProtoId(11) val desc: String = "",
@SerialId(31) val level: Int = 0, @ProtoId(31) val level: Int = 0,
@SerialId(36) val taskinfos: List<Common.MedalTaskInfo>? = null, @ProtoId(36) val taskinfos: List<Common.MedalTaskInfo>? = null,
@SerialId(40) val point: Int = 0, @ProtoId(40) val point: Int = 0,
@SerialId(41) val pointLevel2: Int = 0, @ProtoId(41) val pointLevel2: Int = 0,
@SerialId(42) val pointLevel3: Int = 0, @ProtoId(42) val pointLevel3: Int = 0,
@SerialId(43) val seqLevel2: Long = 0, @ProtoId(43) val seqLevel2: Long = 0,
@SerialId(44) val seqLevel3: Long = 0, @ProtoId(44) val seqLevel3: Long = 0,
@SerialId(45) val timeLevel2: Long = 0L, @ProtoId(45) val timeLevel2: Long = 0L,
@SerialId(46) val timeLevel3: Long = 0L, @ProtoId(46) val timeLevel3: Long = 0L,
@SerialId(47) val descLevel2: String = "", @ProtoId(47) val descLevel2: String = "",
@SerialId(48) val descLevel3: String = "", @ProtoId(48) val descLevel3: String = "",
@SerialId(49) val endtime: Int = 0, @ProtoId(49) val endtime: Int = 0,
@SerialId(50) val detailUrl: String = "", @ProtoId(50) val detailUrl: String = "",
@SerialId(51) val detailUrl2: String = "", @ProtoId(51) val detailUrl2: String = "",
@SerialId(52) val detailUrl3: String = "", @ProtoId(52) val detailUrl3: String = "",
@SerialId(53) val taskDesc: String = "", @ProtoId(53) val taskDesc: String = "",
@SerialId(54) val taskDesc2: String = "", @ProtoId(54) val taskDesc2: String = "",
@SerialId(55) val taskDesc3: String = "", @ProtoId(55) val taskDesc3: String = "",
@SerialId(56) val levelCount: Int = 0, @ProtoId(56) val levelCount: Int = 0,
@SerialId(57) val noProgress: Int = 0, @ProtoId(57) val noProgress: Int = 0,
@SerialId(58) val resource: String = "", @ProtoId(58) val resource: String = "",
@SerialId(59) val fromuinLevel: Int = 0, @ProtoId(59) val fromuinLevel: Int = 0,
@SerialId(60) val unread: Int = 0, @ProtoId(60) val unread: Int = 0,
@SerialId(61) val unread2: Int = 0, @ProtoId(61) val unread2: Int = 0,
@SerialId(62) val unread3: Int = 0 @ProtoId(62) val unread3: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class MedalTaskInfo( class MedalTaskInfo(
@SerialId(1) val taskid: Int = 0, @ProtoId(1) val taskid: Int = 0,
@SerialId(32) val int32TaskValue: Int = 0, @ProtoId(32) val int32TaskValue: Int = 0,
@SerialId(33) val tarValue: Int = 0, @ProtoId(33) val tarValue: Int = 0,
@SerialId(34) val tarValueLevel2: Int = 0, @ProtoId(34) val tarValueLevel2: Int = 0,
@SerialId(35) val tarValueLevel3: Int = 0 @ProtoId(35) val tarValueLevel3: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -75,475 +75,475 @@ class Common : ProtoBuf {
class AppointDefine : ProtoBuf { class AppointDefine : ProtoBuf {
@Serializable @Serializable
class ADFeedContent( class ADFeedContent(
@SerialId(1) val msgUserInfo: AppointDefine.UserInfo? = null, @ProtoId(1) val msgUserInfo: AppointDefine.UserInfo? = null,
@SerialId(2) val strPicUrl: List<String> = listOf(), @ProtoId(2) val strPicUrl: List<String> = listOf(),
@SerialId(3) val msgText: AppointDefine.RichText? = null, @ProtoId(3) val msgText: AppointDefine.RichText? = null,
@SerialId(4) val attendInfo: String = "", @ProtoId(4) val attendInfo: String = "",
@SerialId(5) val actionUrl: String = "", @ProtoId(5) val actionUrl: String = "",
@SerialId(6) val publishTime: Int = 0, @ProtoId(6) val publishTime: Int = 0,
@SerialId(7) val msgHotTopicList: AppointDefine.HotTopicList? = null, @ProtoId(7) val msgHotTopicList: AppointDefine.HotTopicList? = null,
@SerialId(8) val moreUrl: String = "", @ProtoId(8) val moreUrl: String = "",
@SerialId(9) val recordDuration: String = "" @ProtoId(9) val recordDuration: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RichText( class RichText(
@SerialId(1) val msgElems: List<AppointDefine.Elem>? = null @ProtoId(1) val msgElems: List<AppointDefine.Elem>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RankEvent( class RankEvent(
@SerialId(1) val listtype: Int = 0, @ProtoId(1) val listtype: Int = 0,
@SerialId(2) val notifytype: Int = 0, @ProtoId(2) val notifytype: Int = 0,
@SerialId(3) val eventtime: Int = 0, @ProtoId(3) val eventtime: Int = 0,
@SerialId(4) val seq: Int = 0, @ProtoId(4) val seq: Int = 0,
@SerialId(5) val notifyTips: String = "" @ProtoId(5) val notifyTips: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class Wifi( class Wifi(
@SerialId(1) val mac: Long = 0L, @ProtoId(1) val mac: Long = 0L,
@SerialId(2) val int32Rssi: Int = 0 @ProtoId(2) val int32Rssi: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class InterestItem( class InterestItem(
@SerialId(1) val tagId: Long = 0L, @ProtoId(1) val tagId: Long = 0L,
@SerialId(2) val tagName: String = "", @ProtoId(2) val tagName: String = "",
@SerialId(3) val tagIconUrl: String = "", @ProtoId(3) val tagIconUrl: String = "",
@SerialId(4) val tagHref: String = "", @ProtoId(4) val tagHref: String = "",
@SerialId(5) val tagBackColor: String = "", @ProtoId(5) val tagBackColor: String = "",
@SerialId(6) val tagFontColor: String = "", @ProtoId(6) val tagFontColor: String = "",
@SerialId(7) val tagVid: String = "", @ProtoId(7) val tagVid: String = "",
@SerialId(8) val tagType: Int = 0, @ProtoId(8) val tagType: Int = 0,
@SerialId(9) val addTime: Int = 0, @ProtoId(9) val addTime: Int = 0,
@SerialId(10) val tagCategory: String = "", @ProtoId(10) val tagCategory: String = "",
@SerialId(11) val tagOtherUrl: String = "", @ProtoId(11) val tagOtherUrl: String = "",
@SerialId(12) val bid: Int = 0 @ProtoId(12) val bid: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ShopID( class ShopID(
@SerialId(1) val shopid: String = "", @ProtoId(1) val shopid: String = "",
@SerialId(2) val sp: Int = 0 @ProtoId(2) val sp: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FeedComment( class FeedComment(
@SerialId(1) val commentId: String = "", @ProtoId(1) val commentId: String = "",
@SerialId(2) val feedId: String = "", @ProtoId(2) val feedId: String = "",
@SerialId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null, @ProtoId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null,
@SerialId(4) val time: Int = 0, @ProtoId(4) val time: Int = 0,
@SerialId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null, @ProtoId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null,
@SerialId(7) val flag: Int = 0, @ProtoId(7) val flag: Int = 0,
@SerialId(8) val msgContent: AppointDefine.RichText? = null, @ProtoId(8) val msgContent: AppointDefine.RichText? = null,
@SerialId(9) val hot: Int = 0 @ProtoId(9) val hot: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ADFeed( class ADFeed(
@SerialId(1) val taskId: Int = 0, @ProtoId(1) val taskId: Int = 0,
@SerialId(2) val style: Int = 0, @ProtoId(2) val style: Int = 0,
@SerialId(3) val content: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(3) val content: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class Cell( class Cell(
@SerialId(1) val int32Mcc: Int = -1, @ProtoId(1) val int32Mcc: Int = -1,
@SerialId(2) val int32Mnc: Int = -1, @ProtoId(2) val int32Mnc: Int = -1,
@SerialId(3) val int32Lac: Int = -1, @ProtoId(3) val int32Lac: Int = -1,
@SerialId(4) val int32Cellid: Int = -1, @ProtoId(4) val int32Cellid: Int = -1,
@SerialId(5) val int32Rssi: Int = 0 @ProtoId(5) val int32Rssi: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RecentVistorEvent( class RecentVistorEvent(
@SerialId(1) val eventtype: Int = 0, @ProtoId(1) val eventtype: Int = 0,
@SerialId(2) val eventTinyid: Long = 0L, @ProtoId(2) val eventTinyid: Long = 0L,
@SerialId(3) val unreadCount: Int = 0 @ProtoId(3) val unreadCount: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class OrganizerInfo( class OrganizerInfo(
@SerialId(1) val hostName: String = "", @ProtoId(1) val hostName: String = "",
@SerialId(2) val hostUrl: String = "", @ProtoId(2) val hostUrl: String = "",
@SerialId(3) val hostCover: String = "" @ProtoId(3) val hostCover: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class InterestTag( class InterestTag(
@SerialId(1) val tagType: Int = 0, @ProtoId(1) val tagType: Int = 0,
@SerialId(2) val msgTagList: List<AppointDefine.InterestItem>? = null @ProtoId(2) val msgTagList: List<AppointDefine.InterestItem>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class AppointInfoEx( class AppointInfoEx(
@SerialId(1) val feedsPicUrl: String = "", @ProtoId(1) val feedsPicUrl: String = "",
@SerialId(2) val feedsUrl: String = "", @ProtoId(2) val feedsUrl: String = "",
@SerialId(3) val detailTitle: String = "", @ProtoId(3) val detailTitle: String = "",
@SerialId(4) val detailDescribe: String = "", @ProtoId(4) val detailDescribe: String = "",
@SerialId(5) val showPublisher: Int = 0, @ProtoId(5) val showPublisher: Int = 0,
@SerialId(6) val detailPicUrl: String = "", @ProtoId(6) val detailPicUrl: String = "",
@SerialId(7) val detailUrl: String = "", @ProtoId(7) val detailUrl: String = "",
@SerialId(8) val showAttend: Int = 0 @ProtoId(8) val showAttend: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class DateComment( class DateComment(
@SerialId(1) val commentId: String = "", @ProtoId(1) val commentId: String = "",
@SerialId(2) val msgAppointId: AppointDefine.AppointID? = null, @ProtoId(2) val msgAppointId: AppointDefine.AppointID? = null,
@SerialId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null, @ProtoId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null,
@SerialId(4) val time: Int = 0, @ProtoId(4) val time: Int = 0,
@SerialId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null, @ProtoId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null,
@SerialId(7) val flag: Int = 0, @ProtoId(7) val flag: Int = 0,
@SerialId(8) val msgContent: AppointDefine.RichText? = null @ProtoId(8) val msgContent: AppointDefine.RichText? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class AppointContent( class AppointContent(
@SerialId(1) val appointSubject: Int = 0, @ProtoId(1) val appointSubject: Int = 0,
@SerialId(2) val payType: Int = 0, @ProtoId(2) val payType: Int = 0,
@SerialId(3) val appointDate: Int = 0, @ProtoId(3) val appointDate: Int = 0,
@SerialId(4) val appointGender: Int = 0, @ProtoId(4) val appointGender: Int = 0,
@SerialId(5) val appointIntroduce: String = "", @ProtoId(5) val appointIntroduce: String = "",
@SerialId(6) val msgAppointAddress: AppointDefine.AddressInfo? = null, @ProtoId(6) val msgAppointAddress: AppointDefine.AddressInfo? = null,
@SerialId(7) val msgTravelInfo: AppointDefine.TravelInfo? = null @ProtoId(7) val msgTravelInfo: AppointDefine.TravelInfo? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FeedInfo( class FeedInfo(
@SerialId(1) val feedType: Long = 0L, @ProtoId(1) val feedType: Long = 0L,
@SerialId(2) val feedId: String = "", @ProtoId(2) val feedId: String = "",
@SerialId(3) val msgFeedContent: AppointDefine.FeedContent? = null, @ProtoId(3) val msgFeedContent: AppointDefine.FeedContent? = null,
@SerialId(4) val msgTopicInfo: AppointDefine.NearbyTopic? = null, @ProtoId(4) val msgTopicInfo: AppointDefine.NearbyTopic? = null,
@SerialId(5) val publishTime: Long = 0, @ProtoId(5) val publishTime: Long = 0,
@SerialId(6) val praiseCount: Int = 0, @ProtoId(6) val praiseCount: Int = 0,
@SerialId(7) val praiseFlag: Int = 0, @ProtoId(7) val praiseFlag: Int = 0,
@SerialId(8) val msgPraiseUser: List<AppointDefine.StrangerInfo>? = null, @ProtoId(8) val msgPraiseUser: List<AppointDefine.StrangerInfo>? = null,
@SerialId(9) val commentCount: Int = 0, @ProtoId(9) val commentCount: Int = 0,
@SerialId(10) val msgCommentList: List<AppointDefine.FeedComment>? = null, @ProtoId(10) val msgCommentList: List<AppointDefine.FeedComment>? = null,
@SerialId(11) val commentRetAll: Int = 0, @ProtoId(11) val commentRetAll: Int = 0,
@SerialId(12) val hotFlag: Int = 0, @ProtoId(12) val hotFlag: Int = 0,
@SerialId(13) val svrReserved: Long = 0L, @ProtoId(13) val svrReserved: Long = 0L,
@SerialId(14) val msgHotEntry: AppointDefine.HotEntry? = null @ProtoId(14) val msgHotEntry: AppointDefine.HotEntry? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HotTopicList( class HotTopicList(
@SerialId(1) val topicList: List<AppointDefine.HotTopic>? = null @ProtoId(1) val topicList: List<AppointDefine.HotTopic>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FeedContent( class FeedContent(
@SerialId(1) val strPicUrl: List<String> = listOf(), @ProtoId(1) val strPicUrl: List<String> = listOf(),
@SerialId(2) val msgText: AppointDefine.RichText? = null, @ProtoId(2) val msgText: AppointDefine.RichText? = null,
@SerialId(3) val hrefUrl: String = "", @ProtoId(3) val hrefUrl: String = "",
@SerialId(5) val groupName: String = "", @ProtoId(5) val groupName: String = "",
@SerialId(6) val groupBulletin: String = "", @ProtoId(6) val groupBulletin: String = "",
@SerialId(7) val feedType: Int = 0, @ProtoId(7) val feedType: Int = 0,
@SerialId(8) val poiId: String = "", @ProtoId(8) val poiId: String = "",
@SerialId(9) val poiTitle: String = "", @ProtoId(9) val poiTitle: String = "",
@SerialId(20) val effectiveTime: Int = 0, @ProtoId(20) val effectiveTime: Int = 0,
@SerialId(21) val expiationTime: Int = 0, @ProtoId(21) val expiationTime: Int = 0,
@SerialId(22) val msgLocale: AppointDefine.LocaleInfo? = null, @ProtoId(22) val msgLocale: AppointDefine.LocaleInfo? = null,
@SerialId(23) val feedsIndex: Int = 0, @ProtoId(23) val feedsIndex: Int = 0,
@SerialId(24) val msgAd: AppointDefine.ADFeed? = null, @ProtoId(24) val msgAd: AppointDefine.ADFeed? = null,
@SerialId(25) val privateData: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(25) val privateData: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class TravelInfo( class TravelInfo(
@SerialId(1) val msgDepartLocale: AppointDefine.LocaleInfo? = null, @ProtoId(1) val msgDepartLocale: AppointDefine.LocaleInfo? = null,
@SerialId(2) val msgDestination: AppointDefine.LocaleInfo? = null, @ProtoId(2) val msgDestination: AppointDefine.LocaleInfo? = null,
@SerialId(3) val vehicle: Int = 0, @ProtoId(3) val vehicle: Int = 0,
@SerialId(4) val partnerCount: Int = 0, @ProtoId(4) val partnerCount: Int = 0,
@SerialId(5) val placePicUrl: String = "", @ProtoId(5) val placePicUrl: String = "",
@SerialId(6) val placeUrl: String = "" @ProtoId(6) val placeUrl: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RecentFreshFeed( class RecentFreshFeed(
@SerialId(1) val freshFeedInfo: List<AppointDefine.FreshFeedInfo>? = null, @ProtoId(1) val freshFeedInfo: List<AppointDefine.FreshFeedInfo>? = null,
@SerialId(2) val uid: Long = 0L @ProtoId(2) val uid: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GPS( class GPS(
@SerialId(1) val int32Lat: Int = 900000000, @ProtoId(1) val int32Lat: Int = 900000000,
@SerialId(2) val int32Lon: Int = 900000000, @ProtoId(2) val int32Lon: Int = 900000000,
@SerialId(3) val int32Alt: Int = -10000000, @ProtoId(3) val int32Alt: Int = -10000000,
@SerialId(4) val int32Type: Int = 0 @ProtoId(4) val int32Type: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class AppointID( class AppointID(
@SerialId(1) val requestId: String = "" @ProtoId(1) val requestId: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class LocaleInfo( class LocaleInfo(
@SerialId(1) val name: String = "", @ProtoId(1) val name: String = "",
@SerialId(2) val country: String = "", @ProtoId(2) val country: String = "",
@SerialId(3) val province: String = "", @ProtoId(3) val province: String = "",
@SerialId(4) val city: String = "", @ProtoId(4) val city: String = "",
@SerialId(5) val region: String = "", @ProtoId(5) val region: String = "",
@SerialId(6) val poi: String = "", @ProtoId(6) val poi: String = "",
@SerialId(7) val msgGps: AppointDefine.GPS? = null, @ProtoId(7) val msgGps: AppointDefine.GPS? = null,
@SerialId(8) val address: String = "" @ProtoId(8) val address: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class LBSInfo( class LBSInfo(
@SerialId(1) val msgGps: AppointDefine.GPS? = null, @ProtoId(1) val msgGps: AppointDefine.GPS? = null,
@SerialId(2) val msgWifis: List<AppointDefine.Wifi>? = null, @ProtoId(2) val msgWifis: List<AppointDefine.Wifi>? = null,
@SerialId(3) val msgCells: List<AppointDefine.Cell>? = null @ProtoId(3) val msgCells: List<AppointDefine.Cell>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FeedEvent( class FeedEvent(
@SerialId(1) val eventId: Long = 0L, @ProtoId(1) val eventId: Long = 0L,
@SerialId(2) val time: Int = 0, @ProtoId(2) val time: Int = 0,
@SerialId(3) val eventtype: Int = 0, @ProtoId(3) val eventtype: Int = 0,
@SerialId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null, @ProtoId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null,
@SerialId(5) val msgFeedInfo: AppointDefine.FeedInfo? = null, @ProtoId(5) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@SerialId(6) val eventTips: String = "", @ProtoId(6) val eventTips: String = "",
@SerialId(7) val msgComment: AppointDefine.FeedComment? = null, @ProtoId(7) val msgComment: AppointDefine.FeedComment? = null,
@SerialId(8) val cancelEventId: Long = 0L @ProtoId(8) val cancelEventId: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FeedsCookie( class FeedsCookie(
@SerialId(1) val strList: List<String> = listOf(), @ProtoId(1) val strList: List<String> = listOf(),
@SerialId(2) val pose: Int = 0, @ProtoId(2) val pose: Int = 0,
@SerialId(3) val cookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val cookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val uint64Topics: List<Long>? = null @ProtoId(4) val uint64Topics: List<Long>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class NearbyTopic( class NearbyTopic(
@SerialId(1) val topicId: Long = 0L, @ProtoId(1) val topicId: Long = 0L,
@SerialId(2) val topic: String = "", @ProtoId(2) val topic: String = "",
@SerialId(3) val foreword: String = "", @ProtoId(3) val foreword: String = "",
@SerialId(4) val createTime: Int = 0, @ProtoId(4) val createTime: Int = 0,
@SerialId(5) val updateTime: Int = 0, @ProtoId(5) val updateTime: Int = 0,
@SerialId(6) val hotFlag: Int = 0, @ProtoId(6) val hotFlag: Int = 0,
@SerialId(7) val buttonStyle: Int = 0, @ProtoId(7) val buttonStyle: Int = 0,
@SerialId(8) val buttonSrc: String = "", @ProtoId(8) val buttonSrc: String = "",
@SerialId(9) val backgroundSrc: String = "", @ProtoId(9) val backgroundSrc: String = "",
@SerialId(10) val attendeeInfo: String = "", @ProtoId(10) val attendeeInfo: String = "",
@SerialId(11) val index: Int = 0, @ProtoId(11) val index: Int = 0,
@SerialId(12) val publishScope: Int = 0, @ProtoId(12) val publishScope: Int = 0,
@SerialId(13) val effectiveTime: Int = 0, @ProtoId(13) val effectiveTime: Int = 0,
@SerialId(14) val expiationTime: Int = 0, @ProtoId(14) val expiationTime: Int = 0,
@SerialId(15) val pushedUsrCount: Int = 0, @ProtoId(15) val pushedUsrCount: Int = 0,
@SerialId(16) val timerangeLeft: Int = 0, @ProtoId(16) val timerangeLeft: Int = 0,
@SerialId(17) val timerangeRight: Int = 0, @ProtoId(17) val timerangeRight: Int = 0,
@SerialId(18) val area: String = "" @ProtoId(18) val area: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class NearbyEvent( class NearbyEvent(
@SerialId(1) val eventtype: Int = 0, @ProtoId(1) val eventtype: Int = 0,
@SerialId(2) val msgRankevent: AppointDefine.RankEvent? = null, @ProtoId(2) val msgRankevent: AppointDefine.RankEvent? = null,
@SerialId(3) val eventUin: Long = 0L, @ProtoId(3) val eventUin: Long = 0L,
@SerialId(4) val eventTinyid: Long = 0L @ProtoId(4) val eventTinyid: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class Feed( class Feed(
@SerialId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null, @ProtoId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null,
@SerialId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null, @ProtoId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@SerialId(3) val ownerFlag: Int = 0 @ProtoId(3) val ownerFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ActivityInfo( class ActivityInfo(
@SerialId(2) val name: String = "", @ProtoId(2) val name: String = "",
@SerialId(3) val cover: String = "", @ProtoId(3) val cover: String = "",
@SerialId(4) val url: String = "", @ProtoId(4) val url: String = "",
@SerialId(5) val startTime: Int = 0, @ProtoId(5) val startTime: Int = 0,
@SerialId(6) val endTime: Int = 0, @ProtoId(6) val endTime: Int = 0,
@SerialId(7) val locName: String = "", @ProtoId(7) val locName: String = "",
@SerialId(8) val enroll: Long = 0L, @ProtoId(8) val enroll: Long = 0L,
@SerialId(9) val createUin: Long = 0L, @ProtoId(9) val createUin: Long = 0L,
@SerialId(10) val createTime: Int = 0, @ProtoId(10) val createTime: Int = 0,
@SerialId(11) val organizerInfo: AppointDefine.OrganizerInfo = OrganizerInfo(), @ProtoId(11) val organizerInfo: AppointDefine.OrganizerInfo = OrganizerInfo(),
@SerialId(12) val flag: Long? = null @ProtoId(12) val flag: Long? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HotEntry( class HotEntry(
@SerialId(1) val openFlag: Int = 0, @ProtoId(1) val openFlag: Int = 0,
@SerialId(2) val restTime: Int = 0, @ProtoId(2) val restTime: Int = 0,
@SerialId(3) val foreword: String = "", @ProtoId(3) val foreword: String = "",
@SerialId(4) val backgroundSrc: String = "" @ProtoId(4) val backgroundSrc: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class UserFeed( class UserFeed(
@SerialId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null, @ProtoId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null,
@SerialId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null, @ProtoId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@SerialId(3) val ownerFlag: Int = 0, @ProtoId(3) val ownerFlag: Int = 0,
@SerialId(4) val msgActivityInfo: AppointDefine.ActivityInfo? = null @ProtoId(4) val msgActivityInfo: AppointDefine.ActivityInfo? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class Elem( class Elem(
@SerialId(1) val content: String = "", @ProtoId(1) val content: String = "",
@SerialId(2) val msgFaceInfo: AppointDefine.Face? = null @ProtoId(2) val msgFaceInfo: AppointDefine.Face? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HotFreshFeedList( class HotFreshFeedList(
@SerialId(1) val msgFeeds: List<AppointDefine.HotUserFeed>? = null, @ProtoId(1) val msgFeeds: List<AppointDefine.HotUserFeed>? = null,
@SerialId(2) val updateTime: Int = 0 @ProtoId(2) val updateTime: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RptInterestTag( class RptInterestTag(
@SerialId(1) val interestTags: List<AppointDefine.InterestTag>? = null @ProtoId(1) val interestTags: List<AppointDefine.InterestTag>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class AddressInfo( class AddressInfo(
@SerialId(1) val companyZone: String = "", @ProtoId(1) val companyZone: String = "",
@SerialId(2) val companyName: String = "", @ProtoId(2) val companyName: String = "",
@SerialId(3) val companyAddr: String = "", @ProtoId(3) val companyAddr: String = "",
@SerialId(4) val companyPicUrl: String = "", @ProtoId(4) val companyPicUrl: String = "",
@SerialId(5) val companyUrl: String = "", @ProtoId(5) val companyUrl: String = "",
@SerialId(6) val msgCompanyId: AppointDefine.ShopID? = null @ProtoId(6) val msgCompanyId: AppointDefine.ShopID? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PublisherInfo( class PublisherInfo(
@SerialId(1) val tinyid: Long = 0L, @ProtoId(1) val tinyid: Long = 0L,
@SerialId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val age: Int = 0, @ProtoId(3) val age: Int = 0,
@SerialId(4) val gender: Int = 0, @ProtoId(4) val gender: Int = 0,
@SerialId(5) val constellation: String = "", @ProtoId(5) val constellation: String = "",
@SerialId(6) val profession: Int = 0, @ProtoId(6) val profession: Int = 0,
@SerialId(7) val distance: String = "", @ProtoId(7) val distance: String = "",
@SerialId(8) val marriage: Int = 0, @ProtoId(8) val marriage: Int = 0,
@SerialId(9) val vipinfo: String = "", @ProtoId(9) val vipinfo: String = "",
@SerialId(10) val recommend: Int = 0, @ProtoId(10) val recommend: Int = 0,
@SerialId(11) val godflag: Int = 0, @ProtoId(11) val godflag: Int = 0,
@SerialId(12) val chatflag: Int = 0, @ProtoId(12) val chatflag: Int = 0,
@SerialId(13) val chatupCount: Int = 0, @ProtoId(13) val chatupCount: Int = 0,
@SerialId(14) val charm: Int = 0, @ProtoId(14) val charm: Int = 0,
@SerialId(15) val charmLevel: Int = 0, @ProtoId(15) val charmLevel: Int = 0,
@SerialId(16) val pubNumber: Int = 0, @ProtoId(16) val pubNumber: Int = 0,
@SerialId(17) val msgCommonLabel: AppointDefine.CommonLabel? = null, @ProtoId(17) val msgCommonLabel: AppointDefine.CommonLabel? = null,
@SerialId(18) val recentVistorTime: Int = 0, @ProtoId(18) val recentVistorTime: Int = 0,
@SerialId(19) val strangerDeclare: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(19) val strangerDeclare: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(20) val friendUin: Long = 0L, @ProtoId(20) val friendUin: Long = 0L,
@SerialId(21) val historyFlag: Int = 0, @ProtoId(21) val historyFlag: Int = 0,
@SerialId(22) val followflag: Long = 0L @ProtoId(22) val followflag: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HotUserFeed( class HotUserFeed(
@SerialId(1) val feedId: String = "", @ProtoId(1) val feedId: String = "",
@SerialId(2) val praiseCount: Int = 0, @ProtoId(2) val praiseCount: Int = 0,
@SerialId(3) val publishUid: Long = 0L, @ProtoId(3) val publishUid: Long = 0L,
@SerialId(4) val publishTime: Int = 0 @ProtoId(4) val publishTime: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FreshFeedInfo( class FreshFeedInfo(
@SerialId(1) val uin: Long = 0L, @ProtoId(1) val uin: Long = 0L,
@SerialId(2) val time: Int = 0, @ProtoId(2) val time: Int = 0,
@SerialId(3) val feedId: String = "", @ProtoId(3) val feedId: String = "",
@SerialId(4) val feedType: Long = 0L @ProtoId(4) val feedType: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class CommonLabel( class CommonLabel(
@SerialId(1) val lableId: Int = 0, @ProtoId(1) val lableId: Int = 0,
@SerialId(2) val lableMsgPre: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val lableMsgPre: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val lableMsgLast: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val lableMsgLast: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val interstName: List<ByteArray>? = null, @ProtoId(4) val interstName: List<ByteArray>? = null,
@SerialId(5) val interstType: List<Int>? = null @ProtoId(5) val interstType: List<Int>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class Face( class Face(
@SerialId(1) val index: Int = 0 @ProtoId(1) val index: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class StrangerInfo( class StrangerInfo(
@SerialId(1) val tinyid: Long = 0L, @ProtoId(1) val tinyid: Long = 0L,
@SerialId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val age: Int = 0, @ProtoId(3) val age: Int = 0,
@SerialId(4) val gender: Int = 0, @ProtoId(4) val gender: Int = 0,
@SerialId(5) val dating: Int = 0, @ProtoId(5) val dating: Int = 0,
@SerialId(6) val listIdx: Int = 0, @ProtoId(6) val listIdx: Int = 0,
@SerialId(7) val constellation: String = "", @ProtoId(7) val constellation: String = "",
@SerialId(8) val profession: Int = 0, @ProtoId(8) val profession: Int = 0,
@SerialId(9) val marriage: Int = 0, @ProtoId(9) val marriage: Int = 0,
@SerialId(10) val vipinfo: String = "", @ProtoId(10) val vipinfo: String = "",
@SerialId(11) val recommend: Int = 0, @ProtoId(11) val recommend: Int = 0,
@SerialId(12) val godflag: Int = 0, @ProtoId(12) val godflag: Int = 0,
@SerialId(13) val charm: Int = 0, @ProtoId(13) val charm: Int = 0,
@SerialId(14) val charmLevel: Int = 0, @ProtoId(14) val charmLevel: Int = 0,
@SerialId(15) val uin: Long = 0L @ProtoId(15) val uin: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HotTopic( class HotTopic(
@SerialId(1) val id: Long = 0L, @ProtoId(1) val id: Long = 0L,
@SerialId(2) val title: String = "", @ProtoId(2) val title: String = "",
@SerialId(3) val topicType: Long = 0L, @ProtoId(3) val topicType: Long = 0L,
@SerialId(4) val total: Long = 0L, @ProtoId(4) val total: Long = 0L,
@SerialId(5) val times: Long = 0L, @ProtoId(5) val times: Long = 0L,
@SerialId(6) val historyTimes: Long = 0L, @ProtoId(6) val historyTimes: Long = 0L,
@SerialId(7) val bgUrl: String = "", @ProtoId(7) val bgUrl: String = "",
@SerialId(8) val url: String = "", @ProtoId(8) val url: String = "",
@SerialId(9) val extraInfo: String = "" @ProtoId(9) val extraInfo: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class DateEvent( class DateEvent(
@SerialId(1) val eventId: Long = 0L, @ProtoId(1) val eventId: Long = 0L,
@SerialId(2) val time: Int = 0, @ProtoId(2) val time: Int = 0,
@SerialId(3) val type: Int = 0, @ProtoId(3) val type: Int = 0,
@SerialId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null, @ProtoId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null,
@SerialId(5) val msgDateInfo: AppointDefine.AppointInfo? = null, @ProtoId(5) val msgDateInfo: AppointDefine.AppointInfo? = null,
@SerialId(6) val attendIdx: Int = 0, @ProtoId(6) val attendIdx: Int = 0,
@SerialId(7) val eventTips: String = "", @ProtoId(7) val eventTips: String = "",
@SerialId(8) val msgComment: AppointDefine.DateComment? = null, @ProtoId(8) val msgComment: AppointDefine.DateComment? = null,
@SerialId(9) val cancelEventId: Long = 0L @ProtoId(9) val cancelEventId: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class AppointInfo( class AppointInfo(
@SerialId(1) val msgAppointId: AppointDefine.AppointID? = null, @ProtoId(1) val msgAppointId: AppointDefine.AppointID? = null,
@SerialId(2) val msgAppointment: AppointDefine.AppointContent? = null, @ProtoId(2) val msgAppointment: AppointDefine.AppointContent? = null,
@SerialId(3) val appointStatus: Int = 0, @ProtoId(3) val appointStatus: Int = 0,
@SerialId(4) val joinWording: String = "", @ProtoId(4) val joinWording: String = "",
@SerialId(5) val viewWording: String = "", @ProtoId(5) val viewWording: String = "",
@SerialId(6) val unreadCount: Int = 0, @ProtoId(6) val unreadCount: Int = 0,
@SerialId(7) val owner: Int = 0, @ProtoId(7) val owner: Int = 0,
@SerialId(8) val join: Int = 0, @ProtoId(8) val join: Int = 0,
@SerialId(9) val view: Int = 0, @ProtoId(9) val view: Int = 0,
@SerialId(10) val commentWording: String = "", @ProtoId(10) val commentWording: String = "",
@SerialId(11) val commentNum: Int = 0, @ProtoId(11) val commentNum: Int = 0,
@SerialId(12) val attendStatus: Int = 0, @ProtoId(12) val attendStatus: Int = 0,
@SerialId(13) val msgAppointmentEx: AppointDefine.AppointInfoEx? = null @ProtoId(13) val msgAppointmentEx: AppointDefine.AppointInfoEx? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class UserInfo( class UserInfo(
@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 age: Int = 0, @ProtoId(3) val age: Int = 0,
@SerialId(4) val gender: Int = 0, @ProtoId(4) val gender: Int = 0,
@SerialId(5) val avatar: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(5) val avatar: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReplyInfo( class ReplyInfo(
@SerialId(1) val commentId: String = "", @ProtoId(1) val commentId: String = "",
@SerialId(2) val msgStrangerInfo: AppointDefine.StrangerInfo? = null @ProtoId(2) val msgStrangerInfo: AppointDefine.StrangerInfo? = null
) : ProtoBuf ) : ProtoBuf
} }

View File

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

View File

@ -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,51 +18,51 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
class GroupLabel : ProtoBuf { class GroupLabel : ProtoBuf {
@Serializable @Serializable
class Label( class Label(
@SerialId(1) val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val name: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val enumType: Int /* enum */ = 1, @ProtoId(2) val enumType: Int /* enum */ = 1,
@SerialId(3) val textColor: GroupLabel.Color? = null, @ProtoId(3) val textColor: GroupLabel.Color? = null,
@SerialId(4) val edgingColor: GroupLabel.Color? = null, @ProtoId(4) val edgingColor: GroupLabel.Color? = null,
@SerialId(5) val labelAttr: Int = 0, @ProtoId(5) val labelAttr: Int = 0,
@SerialId(6) val labelType: Int = 0 @ProtoId(6) val labelType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RspBody( class RspBody(
@SerialId(1) val error: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val error: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val groupInfo: List<GroupLabel.GroupInfo>? = null @ProtoId(2) val groupInfo: List<GroupLabel.GroupInfo>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class SourceId( class SourceId(
@SerialId(1) val sourceId: Int = 0 @ProtoId(1) val sourceId: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class GroupInfo( class GroupInfo(
@SerialId(1) val int32Result: Int = 0, @ProtoId(1) val int32Result: Int = 0,
@SerialId(2) val groupCode: Long = 0L, @ProtoId(2) val groupCode: Long = 0L,
@SerialId(3) val groupLabel: List<GroupLabel.Label>? = null @ProtoId(3) val groupLabel: List<GroupLabel.Label>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class Color( class Color(
@SerialId(1) val r: Int = 0, @ProtoId(1) val r: Int = 0,
@SerialId(2) val g: Int = 0, @ProtoId(2) val g: Int = 0,
@SerialId(3) val b: Int = 0 @ProtoId(3) val b: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReqBody( class ReqBody(
@SerialId(1) val sourceId: GroupLabel.SourceId? = null, @ProtoId(1) val sourceId: GroupLabel.SourceId? = null,
@SerialId(2) val uinInfo: GroupLabel.UinInfo? = null, @ProtoId(2) val uinInfo: GroupLabel.UinInfo? = null,
@SerialId(3) val numberLabel: Int = 5, @ProtoId(3) val numberLabel: Int = 5,
@SerialId(4) val groupCode: List<Long>? = null, @ProtoId(4) val groupCode: List<Long>? = null,
@SerialId(5) val labelStyle: Int = 0 @ProtoId(5) val labelStyle: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class UinInfo( class UinInfo(
@SerialId(1) val int64Longitude: Long = 0L, @ProtoId(1) val int64Longitude: Long = 0L,
@SerialId(2) val int64Latitude: Long = 0L @ProtoId(2) val int64Latitude: Long = 0L
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -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 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
@ -20,90 +20,90 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
class BdhExtinfo : ProtoBuf { class BdhExtinfo : ProtoBuf {
@Serializable @Serializable
class CommFileExtReq( class CommFileExtReq(
@SerialId(1) val actionType: Int = 0, @ProtoId(1) val actionType: Int = 0,
@SerialId(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class CommFileExtRsp( class CommFileExtRsp(
@SerialId(1) val int32Retcode: Int = 0, @ProtoId(1) val int32Retcode: Int = 0,
@SerialId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PicInfo( class PicInfo(
@SerialId(1) val idx: Int = 0, @ProtoId(1) val idx: Int = 0,
@SerialId(2) val size: Int = 0, @ProtoId(2) val size: Int = 0,
@SerialId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val type: Int = 0 @ProtoId(4) val type: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class QQVoiceExtReq( class QQVoiceExtReq(
@SerialId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val fmt: Int = 0, @ProtoId(2) val fmt: Int = 0,
@SerialId(3) val rate: Int = 0, @ProtoId(3) val rate: Int = 0,
@SerialId(4) val bits: Int = 0, @ProtoId(4) val bits: Int = 0,
@SerialId(5) val channel: Int = 0, @ProtoId(5) val channel: Int = 0,
@SerialId(6) val pinyin: Int = 0 @ProtoId(6) val pinyin: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class QQVoiceExtRsp( class QQVoiceExtRsp(
@SerialId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val int32Retcode: Int = 0, @ProtoId(2) val int32Retcode: Int = 0,
@SerialId(3) val msgResult: List<QQVoiceResult>? = null @ProtoId(3) val msgResult: List<QQVoiceResult>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class QQVoiceResult( class QQVoiceResult(
@SerialId(1) val text: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val text: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val source: Int = 0 @ProtoId(3) val source: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ShortVideoReqExtInfo( class ShortVideoReqExtInfo(
@SerialId(1) val cmd: Int = 0, @ProtoId(1) val cmd: Int = 0,
@SerialId(2) val sessionId: Long = 0L, @ProtoId(2) val sessionId: Long = 0L,
@SerialId(3) val msgThumbinfo: PicInfo? = null, @ProtoId(3) val msgThumbinfo: PicInfo? = null,
@SerialId(4) val msgVideoinfo: VideoInfo? = null, @ProtoId(4) val msgVideoinfo: VideoInfo? = null,
@SerialId(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null, @ProtoId(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null,
@SerialId(6) val boolIsMergeCmdBeforeData: Boolean = false @ProtoId(6) val boolIsMergeCmdBeforeData: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ShortVideoRspExtInfo( class ShortVideoRspExtInfo(
@SerialId(1) val cmd: Int = 0, @ProtoId(1) val cmd: Int = 0,
@SerialId(2) val sessionId: Long = 0L, @ProtoId(2) val sessionId: Long = 0L,
@SerialId(3) val int32Retcode: Int = 0, @ProtoId(3) val int32Retcode: Int = 0,
@SerialId(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val msgThumbinfo: PicInfo? = null, @ProtoId(5) val msgThumbinfo: PicInfo? = null,
@SerialId(6) val msgVideoinfo: VideoInfo? = null, @ProtoId(6) val msgVideoinfo: VideoInfo? = null,
@SerialId(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null, @ProtoId(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null,
@SerialId(8) val retryFlag: Int = 0 @ProtoId(8) val retryFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ShortVideoSureReqInfo( class ShortVideoSureReqInfo(
@SerialId(1) val fromuin: Long = 0L, @ProtoId(1) val fromuin: Long = 0L,
@SerialId(2) val chatType: Int = 0, @ProtoId(2) val chatType: Int = 0,
@SerialId(3) val touin: Long = 0L, @ProtoId(3) val touin: Long = 0L,
@SerialId(4) val groupCode: Long = 0L, @ProtoId(4) val groupCode: Long = 0L,
@SerialId(5) val clientType: Int = 0, @ProtoId(5) val clientType: Int = 0,
@SerialId(6) val msgThumbinfo: PicInfo? = null, @ProtoId(6) val msgThumbinfo: PicInfo? = null,
@SerialId(7) val msgMergeVideoinfo: List<VideoInfo>? = null, @ProtoId(7) val msgMergeVideoinfo: List<VideoInfo>? = null,
@SerialId(8) val msgDropVideoinfo: List<VideoInfo>? = null, @ProtoId(8) val msgDropVideoinfo: List<VideoInfo>? = null,
@SerialId(9) val businessType: Int = 0, @ProtoId(9) val businessType: Int = 0,
@SerialId(10) val subBusinessType: Int = 0 @ProtoId(10) val subBusinessType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ShortVideoSureRspInfo( class ShortVideoSureRspInfo(
@SerialId(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val msgVideoinfo: VideoInfo? = null, @ProtoId(3) val msgVideoinfo: VideoInfo? = null,
@SerialId(4) val mergeCost: Int = 0 @ProtoId(4) val mergeCost: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
@ -111,31 +111,31 @@ class BdhExtinfo : ProtoBuf {
@Serializable @Serializable
class StoryVideoExtRsp( class StoryVideoExtRsp(
@SerialId(1) val int32Retcode: Int = 0, @ProtoId(1) val int32Retcode: Int = 0,
@SerialId(2) val msg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val msg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class UploadPicExtInfo( class UploadPicExtInfo(
@SerialId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class VideoInfo( class VideoInfo(
@SerialId(1) val idx: Int = 0, @ProtoId(1) val idx: Int = 0,
@SerialId(2) val size: Int = 0, @ProtoId(2) val size: Int = 0,
@SerialId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val format: Int = 0, @ProtoId(4) val format: Int = 0,
@SerialId(5) val resLen: Int = 0, @ProtoId(5) val resLen: Int = 0,
@SerialId(6) val resWidth: Int = 0, @ProtoId(6) val resWidth: Int = 0,
@SerialId(7) val time: Int = 0, @ProtoId(7) val time: Int = 0,
@SerialId(8) val starttime: Long = 0L, @ProtoId(8) val starttime: Long = 0L,
@SerialId(9) val isAudio: Int = 0 @ProtoId(9) val isAudio: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -143,142 +143,142 @@ class BdhExtinfo : ProtoBuf {
class CSDataHighwayHead : ProtoBuf { class CSDataHighwayHead : ProtoBuf {
@Serializable @Serializable
class C2CCommonExtendinfo( class C2CCommonExtendinfo(
@SerialId(1) val infoId: Int = 0, @ProtoId(1) val infoId: Int = 0,
@SerialId(2) val msgFilterExtendinfo: FilterExtendinfo? = null @ProtoId(2) val msgFilterExtendinfo: FilterExtendinfo? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class DataHighwayHead( class DataHighwayHead(
@SerialId(1) val version: Int = 0, @ProtoId(1) val version: Int = 0,
@SerialId(2) val uin: String = "", // yes @ProtoId(2) val uin: String = "", // yes
@SerialId(3) val command: String = "", @ProtoId(3) val command: String = "",
@SerialId(4) val seq: Int = 0, @ProtoId(4) val seq: Int = 0,
@SerialId(5) val retryTimes: Int = 0, @ProtoId(5) val retryTimes: Int = 0,
@SerialId(6) val appid: Int = 0, @ProtoId(6) val appid: Int = 0,
@SerialId(7) val dataflag: Int = 0, @ProtoId(7) val dataflag: Int = 0,
@SerialId(8) val commandId: Int = 0, @ProtoId(8) val commandId: Int = 0,
@SerialId(9) val buildVer: String = "", @ProtoId(9) val buildVer: String = "",
@SerialId(10) val localeId: Int = 0 @ProtoId(10) val localeId: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class DataHole( class DataHole(
@SerialId(1) val begin: Long = 0L, @ProtoId(1) val begin: Long = 0L,
@SerialId(2) val end: Long = 0L @ProtoId(2) val end: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FilterExtendinfo( class FilterExtendinfo(
@SerialId(1) val filterFlag: Int = 0, @ProtoId(1) val filterFlag: Int = 0,
@SerialId(2) val msgImageFilterRequest: ImageFilterRequest? = null @ProtoId(2) val msgImageFilterRequest: ImageFilterRequest? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class FilterStyle( class FilterStyle(
@SerialId(1) val styleId: Int = 0, @ProtoId(1) val styleId: Int = 0,
@SerialId(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ImageFilterRequest( class ImageFilterRequest(
@SerialId(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val clientIp: Int = 0, @ProtoId(2) val clientIp: Int = 0,
@SerialId(3) val uin: Long = 0L, @ProtoId(3) val uin: Long = 0L,
@SerialId(4) val style: FilterStyle? = null, @ProtoId(4) val style: FilterStyle? = null,
@SerialId(5) val width: Int = 0, @ProtoId(5) val width: Int = 0,
@SerialId(6) val height: Int = 0, @ProtoId(6) val height: Int = 0,
@SerialId(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ImageFilterResponse( class ImageFilterResponse(
@SerialId(1) val retCode: Int = 0, @ProtoId(1) val retCode: Int = 0,
@SerialId(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val costTime: Int = 0 @ProtoId(3) val costTime: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class LoginSigHead( class LoginSigHead(
@SerialId(1) val loginsigType: Int = 0, @ProtoId(1) val loginsigType: Int = 0,
@SerialId(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class NewServiceTicket( class NewServiceTicket(
@SerialId(1) val signature: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val signature: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PicInfoExt( class PicInfoExt(
@SerialId(1) val picWidth: Int = 0, @ProtoId(1) val picWidth: Int = 0,
@SerialId(2) val picHeight: Int = 0, @ProtoId(2) val picHeight: Int = 0,
@SerialId(3) val picFlag: Int = 0, @ProtoId(3) val picFlag: Int = 0,
@SerialId(4) val busiType: Int = 0, @ProtoId(4) val busiType: Int = 0,
@SerialId(5) val srcTerm: Int = 0, @ProtoId(5) val srcTerm: Int = 0,
@SerialId(6) val platType: Int = 0, @ProtoId(6) val platType: Int = 0,
@SerialId(7) val netType: Int = 0, @ProtoId(7) val netType: Int = 0,
@SerialId(8) val imgType: Int = 0, @ProtoId(8) val imgType: Int = 0,
@SerialId(9) val appPicType: Int = 0 @ProtoId(9) val appPicType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PicRspExtInfo( class PicRspExtInfo(
@SerialId(1) val skey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val skey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val clientIp: Int = 0, @ProtoId(2) val clientIp: Int = 0,
@SerialId(3) val upOffset: Long = 0L, @ProtoId(3) val upOffset: Long = 0L,
@SerialId(4) val blockSize: Long = 0L @ProtoId(4) val blockSize: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class QueryHoleRsp( class QueryHoleRsp(
@SerialId(1) val result: Int = 0, @ProtoId(1) val result: Int = 0,
@SerialId(2) val dataHole: List<DataHole>? = null, @ProtoId(2) val dataHole: List<DataHole>? = null,
@SerialId(3) val boolCompFlag: Boolean = false @ProtoId(3) val boolCompFlag: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReqDataHighwayHead( class ReqDataHighwayHead(
@SerialId(1) val msgBasehead: DataHighwayHead? = null, @ProtoId(1) val msgBasehead: DataHighwayHead? = null,
@SerialId(2) val msgSeghead: SegHead? = null, @ProtoId(2) val msgSeghead: SegHead? = null,
@SerialId(3) val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val reqExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val timestamp: Long = 0L, @ProtoId(4) val timestamp: Long = 0L,
@SerialId(5) val msgLoginSigHead: LoginSigHead? = null @ProtoId(5) val msgLoginSigHead: LoginSigHead? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RspBody( class RspBody(
@SerialId(1) val msgQueryHoleRsp: QueryHoleRsp? = null @ProtoId(1) val msgQueryHoleRsp: QueryHoleRsp? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RspDataHighwayHead( class RspDataHighwayHead(
@SerialId(1) val msgBasehead: DataHighwayHead? = null, @ProtoId(1) val msgBasehead: DataHighwayHead? = null,
@SerialId(2) val msgSeghead: SegHead? = null, @ProtoId(2) val msgSeghead: SegHead? = null,
@SerialId(3) val errorCode: Int = 0, @ProtoId(3) val errorCode: Int = 0,
@SerialId(4) val allowRetry: Int = 0, @ProtoId(4) val allowRetry: Int = 0,
@SerialId(5) val cachecost: Int = 0, @ProtoId(5) val cachecost: Int = 0,
@SerialId(6) val htcost: Int = 0, @ProtoId(6) val htcost: Int = 0,
@SerialId(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val timestamp: Long = 0L, @ProtoId(8) val timestamp: Long = 0L,
@SerialId(9) val range: Long = 0L, @ProtoId(9) val range: Long = 0L,
@SerialId(10) val isReset: Int = 0 @ProtoId(10) val isReset: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class SegHead( class SegHead(
@SerialId(1) val serviceid: Int = 0, @ProtoId(1) val serviceid: Int = 0,
@SerialId(2) val filesize: Long = 0L, @ProtoId(2) val filesize: Long = 0L,
@SerialId(3) val dataoffset: Long = 0L, @ProtoId(3) val dataoffset: Long = 0L,
@SerialId(4) val datalength: Int = 0, @ProtoId(4) val datalength: Int = 0,
@SerialId(5) val rtcode: Int = 0, @ProtoId(5) val rtcode: Int = 0,
@SerialId(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val flag: Int = 0, @ProtoId(7) val flag: Int = 0,
@SerialId(8) val md5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(8) val md5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val cacheAddr: Int = 0, @ProtoId(10) val cacheAddr: Int = 0,
@SerialId(11) val queryTimes: Int = 0, @ProtoId(11) val queryTimes: Int = 0,
@SerialId(12) val updateCacheip: Int = 0 @ProtoId(12) val updateCacheip: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -286,31 +286,31 @@ class CSDataHighwayHead : ProtoBuf {
class HwConfigPersistentPB : ProtoBuf { class HwConfigPersistentPB : ProtoBuf {
@Serializable @Serializable
class HwConfigItemPB( class HwConfigItemPB(
@SerialId(1) val ingKey: String = "", @ProtoId(1) val ingKey: String = "",
@SerialId(2) val endPointList: List<HwEndPointPB>? = null @ProtoId(2) val endPointList: List<HwEndPointPB>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HwConfigPB( class HwConfigPB(
@SerialId(1) val configItemList: List<HwConfigItemPB>? = null, @ProtoId(1) val configItemList: List<HwConfigItemPB>? = null,
@SerialId(2) val netSegConfList: List<HwNetSegConfPB>? = null, @ProtoId(2) val netSegConfList: List<HwNetSegConfPB>? = null,
@SerialId(3) val shortVideoNetConf: List<HwNetSegConfPB>? = null, @ProtoId(3) val shortVideoNetConf: List<HwNetSegConfPB>? = null,
@SerialId(4) val configItemListIp6: List<HwConfigItemPB>? = null @ProtoId(4) val configItemListIp6: List<HwConfigItemPB>? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HwEndPointPB( class HwEndPointPB(
@SerialId(1) val ingHost: String = "", @ProtoId(1) val ingHost: String = "",
@SerialId(2) val int32Port: Int = 0, @ProtoId(2) val int32Port: Int = 0,
@SerialId(3) val int64Timestampe: Long = 0L @ProtoId(3) val int64Timestampe: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class HwNetSegConfPB( class HwNetSegConfPB(
@SerialId(1) val int64NetType: Long = 0L, @ProtoId(1) val int64NetType: Long = 0L,
@SerialId(2) val int64SegSize: Long = 0L, @ProtoId(2) val int64SegSize: Long = 0L,
@SerialId(3) val int64SegNum: Long = 0L, @ProtoId(3) val int64SegNum: Long = 0L,
@SerialId(4) val int64CurConnNum: Long = 0L @ProtoId(4) val int64CurConnNum: Long = 0L
) : ProtoBuf ) : ProtoBuf
} }
@ -318,8 +318,8 @@ class HwConfigPersistentPB : ProtoBuf {
class HwSessionInfoPersistentPB : ProtoBuf { class HwSessionInfoPersistentPB : ProtoBuf {
@Serializable @Serializable
class HwSessionInfoPB( class HwSessionInfoPB(
@SerialId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
} }
@ -327,137 +327,137 @@ class HwSessionInfoPersistentPB : ProtoBuf {
class Subcmd0x501 : ProtoBuf { class Subcmd0x501 : ProtoBuf {
@Serializable @Serializable
class ReqBody( class ReqBody(
@SerialId(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null @ProtoId(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RspBody( class RspBody(
@SerialId(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null @ProtoId(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class SubCmd0x501ReqBody( class SubCmd0x501ReqBody(
@SerialId(1) val uin: Long = 0L, @ProtoId(1) val uin: Long = 0L,
@SerialId(2) val idcId: Int = 0, @ProtoId(2) val idcId: Int = 0,
@SerialId(3) val appid: Int = 0, @ProtoId(3) val appid: Int = 0,
@SerialId(4) val loginSigType: Int = 0, @ProtoId(4) val loginSigType: Int = 0,
@SerialId(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val requestFlag: Int = 0, @ProtoId(6) val requestFlag: Int = 0,
@SerialId(7) val uint32ServiceTypes: List<Int>? = null, @ProtoId(7) val uint32ServiceTypes: List<Int>? = null,
@SerialId(8) val bid: Int = 0, @ProtoId(8) val bid: Int = 0,
@SerialId(9) val term: Int = 0, @ProtoId(9) val term: Int = 0,
@SerialId(10) val plat: Int = 0, @ProtoId(10) val plat: Int = 0,
@SerialId(11) val net: Int = 0, @ProtoId(11) val net: Int = 0,
@SerialId(12) val caller: Int = 0 @ProtoId(12) val caller: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class SubCmd0x501Rspbody( class SubCmd0x501Rspbody(
@SerialId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val msgHttpconnAddrs: List<SrvAddrs>? = null, @ProtoId(3) val msgHttpconnAddrs: List<SrvAddrs>? = null,
@SerialId(4) val preConnection: Int = 0, @ProtoId(4) val preConnection: Int = 0,
@SerialId(5) val csConn: Int = 0, @ProtoId(5) val csConn: Int = 0,
@SerialId(6) val msgIpLearnConf: IpLearnConf? = null, @ProtoId(6) val msgIpLearnConf: IpLearnConf? = null,
@SerialId(7) val msgDynTimeoutConf: DynTimeOutConf? = null, @ProtoId(7) val msgDynTimeoutConf: DynTimeOutConf? = null,
@SerialId(8) val msgOpenUpConf: OpenUpConf? = null, @ProtoId(8) val msgOpenUpConf: OpenUpConf? = null,
@SerialId(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null, @ProtoId(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null,
@SerialId(10) val msgShortVideoConf: ShortVideoConf? = null, @ProtoId(10) val msgShortVideoConf: ShortVideoConf? = null,
@SerialId(11) val msgPtvConf: PTVConf? = null @ProtoId(11) val msgPtvConf: PTVConf? = null
) : ProtoBuf { ) : ProtoBuf {
@Serializable @Serializable
class DownloadEncryptConf( class DownloadEncryptConf(
@SerialId(1) val boolEnableEncryptRequest: Boolean = false, @ProtoId(1) val boolEnableEncryptRequest: Boolean = false,
@SerialId(2) val boolEnableEncryptedPic: Boolean = false, @ProtoId(2) val boolEnableEncryptedPic: Boolean = false,
@SerialId(3) val ctrlFlag: Int = 0 @ProtoId(3) val ctrlFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class DynTimeOutConf( class DynTimeOutConf(
@SerialId(1) val tbase2g: Int = 0, @ProtoId(1) val tbase2g: Int = 0,
@SerialId(2) val tbase3g: Int = 0, @ProtoId(2) val tbase3g: Int = 0,
@SerialId(3) val tbase4g: Int = 0, @ProtoId(3) val tbase4g: Int = 0,
@SerialId(4) val tbaseWifi: Int = 0, @ProtoId(4) val tbaseWifi: Int = 0,
@SerialId(5) val torg2g: Int = 0, @ProtoId(5) val torg2g: Int = 0,
@SerialId(6) val torg3g: Int = 0, @ProtoId(6) val torg3g: Int = 0,
@SerialId(7) val torg4g: Int = 0, @ProtoId(7) val torg4g: Int = 0,
@SerialId(8) val torgWifi: Int = 0, @ProtoId(8) val torgWifi: Int = 0,
@SerialId(9) val maxTimeout: Int = 0, @ProtoId(9) val maxTimeout: Int = 0,
@SerialId(10) val enableDynTimeout: Int = 0, @ProtoId(10) val enableDynTimeout: Int = 0,
@SerialId(11) val maxTimeout2g: Int = 0, @ProtoId(11) val maxTimeout2g: Int = 0,
@SerialId(12) val maxTimeout3g: Int = 0, @ProtoId(12) val maxTimeout3g: Int = 0,
@SerialId(13) val maxTimeout4g: Int = 0, @ProtoId(13) val maxTimeout4g: Int = 0,
@SerialId(14) val maxTimeoutWifi: Int = 0, @ProtoId(14) val maxTimeoutWifi: Int = 0,
@SerialId(15) val hbTimeout2g: Int = 0, @ProtoId(15) val hbTimeout2g: Int = 0,
@SerialId(16) val hbTimeout3g: Int = 0, @ProtoId(16) val hbTimeout3g: Int = 0,
@SerialId(17) val hbTimeout4g: Int = 0, @ProtoId(17) val hbTimeout4g: Int = 0,
@SerialId(18) val hbTimeoutWifi: Int = 0, @ProtoId(18) val hbTimeoutWifi: Int = 0,
@SerialId(19) val hbTimeoutDefault: Int = 0 @ProtoId(19) val hbTimeoutDefault: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class Ip6Addr( class Ip6Addr(
@SerialId(1) val type: Int = 0, @ProtoId(1) val type: Int = 0,
@SerialId(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val port: Int = 0, @ProtoId(3) val port: Int = 0,
@SerialId(4) val area: Int = 0, @ProtoId(4) val area: Int = 0,
@SerialId(5) val sameIsp: Int = 0 @ProtoId(5) val sameIsp: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class IpAddr( class IpAddr(
@SerialId(1) val type: Int = 0, @ProtoId(1) val type: Int = 0,
@ProtoType(ProtoNumberType.FIXED) @SerialId(2) val ip: Int = 0, @ProtoType(ProtoNumberType.FIXED) @ProtoId(2) val ip: Int = 0,
@SerialId(3) val port: Int = 0, @ProtoId(3) val port: Int = 0,
@SerialId(4) val area: Int = 0, @ProtoId(4) val area: Int = 0,
@SerialId(5) val sameIsp: Int = 0 @ProtoId(5) val sameIsp: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class IpLearnConf( class IpLearnConf(
@SerialId(1) val refreshCachedIp: Int = 0, @ProtoId(1) val refreshCachedIp: Int = 0,
@SerialId(2) val enableIpLearn: Int = 0 @ProtoId(2) val enableIpLearn: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class NetSegConf( class NetSegConf(
@SerialId(1) val netType: Int = 0, @ProtoId(1) val netType: Int = 0,
@SerialId(2) val segsize: Int = 0, @ProtoId(2) val segsize: Int = 0,
@SerialId(3) val segnum: Int = 0, @ProtoId(3) val segnum: Int = 0,
@SerialId(4) val curconnnum: Int = 0 @ProtoId(4) val curconnnum: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class OpenUpConf( class OpenUpConf(
@SerialId(1) val boolEnableOpenup: Boolean = false, @ProtoId(1) val boolEnableOpenup: Boolean = false,
@SerialId(2) val preSendSegnum: Int = 0, @ProtoId(2) val preSendSegnum: Int = 0,
@SerialId(3) val preSendSegnum3g: Int = 0, @ProtoId(3) val preSendSegnum3g: Int = 0,
@SerialId(4) val preSendSegnum4g: Int = 0, @ProtoId(4) val preSendSegnum4g: Int = 0,
@SerialId(5) val preSendSegnumWifi: Int = 0 @ProtoId(5) val preSendSegnumWifi: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PTVConf( class PTVConf(
@SerialId(1) val channelType: Int = 0, @ProtoId(1) val channelType: Int = 0,
@SerialId(2) val msgNetsegconf: List<NetSegConf>? = null, @ProtoId(2) val msgNetsegconf: List<NetSegConf>? = null,
@SerialId(3) val boolOpenHardwareCodec: Boolean = false @ProtoId(3) val boolOpenHardwareCodec: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ShortVideoConf( class ShortVideoConf(
@SerialId(1) val channelType: Int = 0, @ProtoId(1) val channelType: Int = 0,
@SerialId(2) val msgNetsegconf: List<NetSegConf>? = null, @ProtoId(2) val msgNetsegconf: List<NetSegConf>? = null,
@SerialId(3) val boolOpenHardwareCodec: Boolean = false, @ProtoId(3) val boolOpenHardwareCodec: Boolean = false,
@SerialId(4) val boolSendAheadSignal: Boolean = false @ProtoId(4) val boolSendAheadSignal: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class SrvAddrs( class SrvAddrs(
@SerialId(1) val serviceType: Int = 0, @ProtoId(1) val serviceType: Int = 0,
@SerialId(2) val msgAddrs: List<IpAddr>? = null, @ProtoId(2) val msgAddrs: List<IpAddr>? = null,
@SerialId(3) val fragmentSize: Int = 0, @ProtoId(3) val fragmentSize: Int = 0,
@SerialId(4) val msgNetsegconf: List<NetSegConf>? = null, @ProtoId(4) val msgNetsegconf: List<NetSegConf>? = null,
@SerialId(5) val msgAddrsV6: List<Ip6Addr>? = null @ProtoId(5) val msgAddrsV6: List<Ip6Addr>? = null
) : ProtoBuf ) : ProtoBuf
} }
} }

View File

@ -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
@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=ftn1=picplatform255 @ProtoId(9) val srcFileType: Int = 1,//2=ftn1=picplatform255
@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.0.1296",//版本号 @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

View File

@ -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
@ -21,143 +21,143 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
internal class MsgComm : ProtoBuf { internal class MsgComm : ProtoBuf {
@Serializable @Serializable
internal class AppShareInfo( internal class AppShareInfo(
@SerialId(1) val appshareId: Int = 0, @ProtoId(1) val appshareId: Int = 0,
@SerialId(2) val appshareCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val appshareCookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val appshareResource: PluginInfo? = null @ProtoId(3) val appshareResource: PluginInfo? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class C2CTmpMsgHead( internal class C2CTmpMsgHead(
@SerialId(1) val c2cType: Int = 0, @ProtoId(1) val c2cType: Int = 0,
@SerialId(2) val serviceType: Int = 0, @ProtoId(2) val serviceType: Int = 0,
@SerialId(3) val groupUin: Long = 0L, @ProtoId(3) val groupUin: Long = 0L,
@SerialId(4) val groupCode: Long = 0L, @ProtoId(4) val groupCode: Long = 0L,
@SerialId(5) val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(5) val sig: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val sigType: Int = 0, @ProtoId(6) val sigType: Int = 0,
@SerialId(7) val fromPhone: String = "", @ProtoId(7) val fromPhone: String = "",
@SerialId(8) val toPhone: String = "", @ProtoId(8) val toPhone: String = "",
@SerialId(9) val lockDisplay: Int = 0, @ProtoId(9) val lockDisplay: Int = 0,
@SerialId(10) val directionFlag: Int = 0, @ProtoId(10) val directionFlag: Int = 0,
@SerialId(11) val reserved: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(11) val reserved: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ContentHead( internal class ContentHead(
@SerialId(1) val pkgNum: Int = 0, @ProtoId(1) val pkgNum: Int = 0,
@SerialId(2) val pkgIndex: Int = 0, @ProtoId(2) val pkgIndex: Int = 0,
@SerialId(3) val divSeq: Int = 0, @ProtoId(3) val divSeq: Int = 0,
@SerialId(4) val autoReply: Int = 0 @ProtoId(4) val autoReply: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class DiscussInfo( internal class DiscussInfo(
@SerialId(1) val discussUin: Long = 0L, @ProtoId(1) val discussUin: Long = 0L,
@SerialId(2) val discussType: Int = 0, @ProtoId(2) val discussType: Int = 0,
@SerialId(3) val discussInfoSeq: Long = 0L, @ProtoId(3) val discussInfoSeq: Long = 0L,
@SerialId(4) val discussRemark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val discussRemark: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val discussName: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(5) val discussName: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class ExtGroupKeyInfo( internal class ExtGroupKeyInfo(
@SerialId(1) val curMaxSeq: Int = 0, @ProtoId(1) val curMaxSeq: Int = 0,
@SerialId(2) val curTime: Long = 0L @ProtoId(2) val curTime: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class GroupInfo( internal class GroupInfo(
@SerialId(1) val groupCode: Long = 0L, @ProtoId(1) val groupCode: Long = 0L,
@SerialId(2) val groupType: Int = 0, @ProtoId(2) val groupType: Int = 0,
@SerialId(3) val groupInfoSeq: Long = 0L, @ProtoId(3) val groupInfoSeq: Long = 0L,
@SerialId(4) val groupCard: String = "", @ProtoId(4) val groupCard: String = "",
@SerialId(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val groupLevel: Int = 0, @ProtoId(6) val groupLevel: Int = 0,
@SerialId(7) val groupCardType: Int = 0, @ProtoId(7) val groupCardType: Int = 0,
@SerialId(8) val groupName: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(8) val groupName: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class Msg( internal class Msg(
@SerialId(1) val msgHead: MsgHead, @ProtoId(1) val msgHead: MsgHead,
@SerialId(2) val contentHead: ContentHead? = null, @ProtoId(2) val contentHead: ContentHead? = null,
@SerialId(3) val msgBody: ImMsgBody.MsgBody, @ProtoId(3) val msgBody: ImMsgBody.MsgBody,
@SerialId(4) val appshareInfo: AppShareInfo? = null @ProtoId(4) val appshareInfo: AppShareInfo? = null
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class MsgHead( internal class MsgHead(
@SerialId(1) val fromUin: Long = 0L, @ProtoId(1) val fromUin: Long = 0L,
@SerialId(2) val toUin: Long = 0L, @ProtoId(2) val toUin: Long = 0L,
@SerialId(3) val msgType: Int = 0, @ProtoId(3) val msgType: Int = 0,
@SerialId(4) val c2cCmd: Int = 0, @ProtoId(4) val c2cCmd: Int = 0,
@SerialId(5) val msgSeq: Int = 0, @ProtoId(5) val msgSeq: Int = 0,
@SerialId(6) val msgTime: Int = 0, @ProtoId(6) val msgTime: Int = 0,
@SerialId(7) var msgUid: Long = 0L, @ProtoId(7) var msgUid: Long = 0L,
@SerialId(8) val c2cTmpMsgHead: C2CTmpMsgHead? = null, @ProtoId(8) val c2cTmpMsgHead: C2CTmpMsgHead? = null,
@SerialId(9) val groupInfo: GroupInfo? = null, @ProtoId(9) val groupInfo: GroupInfo? = null,
@SerialId(10) val fromAppid: Int = 0, @ProtoId(10) val fromAppid: Int = 0,
@SerialId(11) val fromInstid: Int = 0, @ProtoId(11) val fromInstid: Int = 0,
@SerialId(12) val userActive: Int = 0, @ProtoId(12) val userActive: Int = 0,
@SerialId(13) val discussInfo: DiscussInfo? = null, @ProtoId(13) val discussInfo: DiscussInfo? = null,
@SerialId(14) val fromNick: String = "", @ProtoId(14) val fromNick: String = "",
@SerialId(15) val authUin: Long = 0L, @ProtoId(15) val authUin: Long = 0L,
@SerialId(16) val authNick: String = "", @ProtoId(16) val authNick: String = "",
@SerialId(17) val msgFlag: Int = 0, @ProtoId(17) val msgFlag: Int = 0,
@SerialId(18) val authRemark: String = "", @ProtoId(18) val authRemark: String = "",
@SerialId(19) val groupName: String = "", @ProtoId(19) val groupName: String = "",
@SerialId(20) val mutiltransHead: MutilTransHead? = null, @ProtoId(20) val mutiltransHead: MutilTransHead? = null,
@SerialId(21) val msgInstCtrl: ImMsgHead.InstCtrl? = null, @ProtoId(21) val msgInstCtrl: ImMsgHead.InstCtrl? = null,
@SerialId(22) val publicAccountGroupSendFlag: Int = 0, @ProtoId(22) val publicAccountGroupSendFlag: Int = 0,
@SerialId(23) val wseqInC2cMsghead: Int = 0, @ProtoId(23) val wseqInC2cMsghead: Int = 0,
@SerialId(24) val cpid: Long = 0L, @ProtoId(24) val cpid: Long = 0L,
@SerialId(25) val extGroupKeyInfo: ExtGroupKeyInfo? = null, @ProtoId(25) val extGroupKeyInfo: ExtGroupKeyInfo? = null,
@SerialId(26) val multiCompatibleText: String = "", @ProtoId(26) val multiCompatibleText: String = "",
@SerialId(27) val authSex: Int = 0, @ProtoId(27) val authSex: Int = 0,
@SerialId(28) val isSrcMsg: Boolean = false @ProtoId(28) val isSrcMsg: Boolean = false
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class MsgType0x210( internal class MsgType0x210(
@SerialId(1) val subMsgType: Int = 0, @ProtoId(1) val subMsgType: Int = 0,
@SerialId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class MutilTransHead( internal class MutilTransHead(
@SerialId(1) val status: Int = 0, @ProtoId(1) val status: Int = 0,
@SerialId(2) val msgId: Int = 0 @ProtoId(2) val msgId: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class PluginInfo( internal class PluginInfo(
@SerialId(1) val resId: Int = 0, @ProtoId(1) val resId: Int = 0,
@SerialId(2) val pkgName: String = "", @ProtoId(2) val pkgName: String = "",
@SerialId(3) val newVer: Int = 0, @ProtoId(3) val newVer: Int = 0,
@SerialId(4) val resType: Int = 0, @ProtoId(4) val resType: Int = 0,
@SerialId(5) val lanType: Int = 0, @ProtoId(5) val lanType: Int = 0,
@SerialId(6) val priority: Int = 0, @ProtoId(6) val priority: Int = 0,
@SerialId(7) val resName: String = "", @ProtoId(7) val resName: String = "",
@SerialId(8) val resDesc: String = "", @ProtoId(8) val resDesc: String = "",
@SerialId(9) val resUrlBig: String = "", @ProtoId(9) val resUrlBig: String = "",
@SerialId(10) val resUrlSmall: String = "", @ProtoId(10) val resUrlSmall: String = "",
@SerialId(11) val resConf: String = "" @ProtoId(11) val resConf: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class Uin2Nick( internal class Uin2Nick(
@SerialId(1) val uin: Long = 0L, @ProtoId(1) val uin: Long = 0L,
@SerialId(2) val nick: String = "" @ProtoId(2) val nick: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
internal class UinPairMsg( internal class UinPairMsg(
@SerialId(1) val lastReadTime: Int = 0, @ProtoId(1) val lastReadTime: Int = 0,
@SerialId(2) val peerUin: Long = 0L, @ProtoId(2) val peerUin: Long = 0L,
@SerialId(3) val msgCompleted: Int = 0, @ProtoId(3) val msgCompleted: Int = 0,
@SerialId(4) val msg: List<Msg>? = null, @ProtoId(4) val msg: List<Msg>? = null,
@SerialId(5) val unreadMsgNum: Int = 0, @ProtoId(5) val unreadMsgNum: Int = 0,
@SerialId(8) val c2cType: Int = 0, @ProtoId(8) val c2cType: Int = 0,
@SerialId(9) val serviceType: Int = 0, @ProtoId(9) val serviceType: Int = 0,
@SerialId(10) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(10) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -0,0 +1,38 @@
/*
* 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.network.protocol.data.proto
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
class MsgRevokeUserDef : ProtoBuf {
@Serializable
class MsgInfoUserDef(
@ProtoId(1) val longMessageFlag: Int = 0,
@ProtoId(2) val longMsgInfo: List<MsgInfoDef>? = null,
@ProtoId(3) val fileUuid: List<String> = listOf()
) : ProtoBuf {
@Serializable
class MsgInfoDef(
@ProtoId(1) val msgSeq: Int = 0,
@ProtoId(2) val longMsgId: Int = 0,
@ProtoId(3) val longMsgNum: Int = 0,
@ProtoId(4) val longMsgIndex: Int = 0
) : ProtoBuf
}
@Serializable
class UinTypeUserDef(
@ProtoId(1) val fromUinType: Int = 0,
@ProtoId(2) val fromGroupCode: Long = 0L,
@ProtoId(3) val fileUuid: List<String> = listOf()
) : ProtoBuf
}

View File

@ -7,16 +7,16 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
package net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769 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
class Oidb0x769 { class Oidb0x769 {
@Serializable @Serializable
class RequestBody( class RequestBody(
@SerialId(1) val rpt_config_list: List<ConfigSeq> @ProtoId(1) val rpt_config_list: List<ConfigSeq>
// @SerialId(2) val msg_device_info: DeviceInfo, // @SerialId(2) val msg_device_info: DeviceInfo,
// @SerialId(3) val str_info: String = "", // @SerialId(3) val str_info: String = "",
// @SerialId(4) val province: String, // @SerialId(4) val province: String,
@ -27,20 +27,20 @@ class Oidb0x769 {
@Serializable @Serializable
class QueryUinPackageUsageReq( class QueryUinPackageUsageReq(
@SerialId(1) val type: Int, @ProtoId(1) val type: Int,
@SerialId(2) val uinFileSize: Long = 0 @ProtoId(2) val uinFileSize: Long = 0
): ProtoBuf ): ProtoBuf
@Serializable @Serializable
class ConfigSeq( class ConfigSeq(
@SerialId(1) val type: Int, // uint @ProtoId(1) val type: Int, // uint
@SerialId(2) val version: Int // uint @ProtoId(2) val version: Int // uint
): ProtoBuf ): ProtoBuf
@Serializable @Serializable
class DeviceInfo( class DeviceInfo(
@SerialId(1) val brand: String, @ProtoId(1) val brand: String,
@SerialId(2) val model: String @ProtoId(2) val model: String
//@SerialId(3) val os: OS, //@SerialId(3) val os: OS,
//@SerialId(4) val cpu: CPU, //@SerialId(4) val cpu: CPU,
//@SerialId(5) val memory: Memory, //@SerialId(5) val memory: Memory,
@ -51,45 +51,45 @@ class Oidb0x769 {
@Serializable @Serializable
class OS( class OS(
@SerialId(1) val type: Int = 1, @ProtoId(1) val type: Int = 1,
@SerialId(2) val version: String, @ProtoId(2) val version: String,
@SerialId(3) val sdk: String, @ProtoId(3) val sdk: String,
@SerialId(4) val kernel: String, @ProtoId(4) val kernel: String,
@SerialId(5) val rom: String @ProtoId(5) val rom: String
): ProtoBuf ): ProtoBuf
@Serializable @Serializable
class Camera( class Camera(
@SerialId(1) val primary: Long, @ProtoId(1) val primary: Long,
@SerialId(2) val secondary: Long, @ProtoId(2) val secondary: Long,
@SerialId(3) val flag: Boolean @ProtoId(3) val flag: Boolean
): ProtoBuf ): ProtoBuf
@Serializable @Serializable
class CPU( class CPU(
@SerialId(1) val model: String, @ProtoId(1) val model: String,
@SerialId(2) val frequency: Int, @ProtoId(2) val frequency: Int,
@SerialId(3) val cores: Int @ProtoId(3) val cores: Int
): ProtoBuf ): ProtoBuf
@Serializable @Serializable
class Memory( class Memory(
@SerialId(1) val total: Int, @ProtoId(1) val total: Int,
@SerialId(2) val process: Int @ProtoId(2) val process: Int
): ProtoBuf ): ProtoBuf
@Serializable @Serializable
class Screen( class Screen(
@SerialId(1) val model: String, @ProtoId(1) val model: String,
@SerialId(2) val width: Int, @ProtoId(2) val width: Int,
@SerialId(3) val height: Int, @ProtoId(3) val height: Int,
@SerialId(4) val dpi: Int, @ProtoId(4) val dpi: Int,
@SerialId(5) val multiTouch: Boolean @ProtoId(5) val multiTouch: Boolean
): ProtoBuf ): ProtoBuf
@Serializable @Serializable
class Storage( class Storage(
@SerialId(1) val builtin: Int, @ProtoId(1) val builtin: Int,
@SerialId(2) val external: Int @ProtoId(2) val external: Int
): ProtoBuf ): ProtoBuf
} }

View File

@ -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,11 +18,11 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
internal class MsgOnlinePush { internal class MsgOnlinePush {
@Serializable @Serializable
internal class PbPushMsg( internal class PbPushMsg(
@SerialId(1) val msg: MsgComm.Msg, @ProtoId(1) val msg: MsgComm.Msg,
@SerialId(2) val svrip: Int = 0, @ProtoId(2) val svrip: Int = 0,
@SerialId(3) val pushToken: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val pushToken: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val pingFlag: Int = 0, @ProtoId(4) val pingFlag: Int = 0,
@SerialId(9) val generalFlag: Int = 0 @ProtoId(9) val generalFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -30,24 +30,24 @@ internal class MsgOnlinePush {
class OnlinePushTrans : ProtoBuf { class OnlinePushTrans : ProtoBuf {
@Serializable @Serializable
class ExtGroupKeyInfo( class ExtGroupKeyInfo(
@SerialId(1) val curMaxSeq: Int = 0, @ProtoId(1) val curMaxSeq: Int = 0,
@SerialId(2) val curTime: Long = 0L @ProtoId(2) val curTime: Long = 0L
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class PbMsgInfo( class PbMsgInfo(
@SerialId(1) val fromUin: Long = 0L, @ProtoId(1) val fromUin: Long = 0L,
@SerialId(2) val toUin: Long = 0L, @ProtoId(2) val toUin: Long = 0L,
@SerialId(3) val msgType: Int = 0, @ProtoId(3) val msgType: Int = 0,
@SerialId(4) val msgSubtype: Int = 0, @ProtoId(4) val msgSubtype: Int = 0,
@SerialId(5) val msgSeq: Int = 0, @ProtoId(5) val msgSeq: Int = 0,
@SerialId(6) val msgUid: Long = 0L, @ProtoId(6) val msgUid: Long = 0L,
@SerialId(7) val msgTime: Int = 0, @ProtoId(7) val msgTime: Int = 0,
@SerialId(8) val realMsgTime: Int = 0, @ProtoId(8) val realMsgTime: Int = 0,
@SerialId(9) val nickName: String = "", @ProtoId(9) val nickName: String = "",
@SerialId(10) val msgData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(10) val msgData: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(11) val svrIp: Int = 0, @ProtoId(11) val svrIp: Int = 0,
@SerialId(12) val extGroupKeyInfo: OnlinePushTrans.ExtGroupKeyInfo? = null, @ProtoId(12) val extGroupKeyInfo: OnlinePushTrans.ExtGroupKeyInfo? = null,
@SerialId(17) val generalFlag: Int = 0 @ProtoId(17) val generalFlag: Int = 0
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -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,68 +18,68 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY
class Generalflags : ProtoBuf { class Generalflags : ProtoBuf {
@Serializable @Serializable
class ResvAttr( class ResvAttr(
@SerialId(1) val globalGroupLevel: Int = 0, @ProtoId(1) val globalGroupLevel: Int = 0,
@SerialId(2) val nearbyCharmLevel: Int = 0, @ProtoId(2) val nearbyCharmLevel: Int = 0,
@SerialId(3) val redbagMsgSenderUin: Long = 0L, @ProtoId(3) val redbagMsgSenderUin: Long = 0L,
@SerialId(4) val titleId: Int = 0, @ProtoId(4) val titleId: Int = 0,
@SerialId(5) val robotMsgFlag: Int = 0, @ProtoId(5) val robotMsgFlag: Int = 0,
@SerialId(6) val wantGiftSenderUin: Long = 0L, @ProtoId(6) val wantGiftSenderUin: Long = 0L,
@SerialId(7) val stickerX: Float = 0.0f, @ProtoId(7) val stickerX: Float = 0.0f,
@SerialId(8) val stickerY: Float = 0.0f, @ProtoId(8) val stickerY: Float = 0.0f,
@SerialId(9) val stickerWidth: Float = 0.0f, @ProtoId(9) val stickerWidth: Float = 0.0f,
@SerialId(10) val stickerHeight: Float = 0.0f, @ProtoId(10) val stickerHeight: Float = 0.0f,
@SerialId(11) val stickerRotate: Int = 0, @ProtoId(11) val stickerRotate: Int = 0,
@SerialId(12) val stickerHostMsgseq: Long = 0L, @ProtoId(12) val stickerHostMsgseq: Long = 0L,
@SerialId(13) val stickerHostMsguid: Long = 0L, @ProtoId(13) val stickerHostMsguid: Long = 0L,
@SerialId(14) val stickerHostTime: Long = 0L, @ProtoId(14) val stickerHostTime: Long = 0L,
@SerialId(15) val mobileCustomFont: Int = 0, @ProtoId(15) val mobileCustomFont: Int = 0,
@SerialId(16) val tailKey: Int = 0, @ProtoId(16) val tailKey: Int = 0,
@SerialId(17) val showTailFlag: Int = 0, @ProtoId(17) val showTailFlag: Int = 0,
@SerialId(18) val doutuMsgType: Int = 0, @ProtoId(18) val doutuMsgType: Int = 0,
@SerialId(19) val doutuCombo: Int = 0, @ProtoId(19) val doutuCombo: Int = 0,
@SerialId(20) val customFeatureid: Int = 0, @ProtoId(20) val customFeatureid: Int = 0,
@SerialId(21) val goldenMsgType: Int = 0, @ProtoId(21) val goldenMsgType: Int = 0,
@SerialId(22) val goldenMsgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(22) val goldenMsgInfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(23) val botMessageClassId: Int = 0, @ProtoId(23) val botMessageClassId: Int = 0,
@SerialId(24) val subscriptionUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(24) val subscriptionUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(25) val pendantDiyId: Int = 0, @ProtoId(25) val pendantDiyId: Int = 0,
@SerialId(26) val timedMessage: Int = 0, @ProtoId(26) val timedMessage: Int = 0,
@SerialId(27) val holidayFlag: Int = 0, @ProtoId(27) val holidayFlag: Int = 0,
@SerialId(29) val kplInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(29) val kplInfo: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(30) val faceId: Int = 0, @ProtoId(30) val faceId: Int = 0,
@SerialId(31) val diyFontTimestamp: Int = 0, @ProtoId(31) val diyFontTimestamp: Int = 0,
@SerialId(32) val redEnvelopeType: Int = 0, @ProtoId(32) val redEnvelopeType: Int = 0,
@SerialId(33) val shortVideoId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(33) val shortVideoId: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(34) val reqFontEffectId: Int = 0, @ProtoId(34) val reqFontEffectId: Int = 0,
@SerialId(35) val loveLanguageFlag: Int = 0, @ProtoId(35) val loveLanguageFlag: Int = 0,
@SerialId(36) val aioSyncToStoryFlag: Int = 0, @ProtoId(36) val aioSyncToStoryFlag: Int = 0,
@SerialId(37) val uploadImageToQzoneFlag: Int = 0, @ProtoId(37) val uploadImageToQzoneFlag: Int = 0,
@SerialId(39) val uploadImageToQzoneParam: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(39) val uploadImageToQzoneParam: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(40) val groupConfessSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(40) val groupConfessSig: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(41) val subfontId: Long = 0L, @ProtoId(41) val subfontId: Long = 0L,
@SerialId(42) val msgFlagType: Int = 0, @ProtoId(42) val msgFlagType: Int = 0,
@SerialId(43) val uint32CustomFeatureid: List<Int>? = null, @ProtoId(43) val uint32CustomFeatureid: List<Int>? = null,
@SerialId(44) val richCardNameVer: Int = 0, @ProtoId(44) val richCardNameVer: Int = 0,
@SerialId(47) val msgInfoFlag: Int = 0, @ProtoId(47) val msgInfoFlag: Int = 0,
@SerialId(48) val serviceMsgType: Int = 0, @ProtoId(48) val serviceMsgType: Int = 0,
@SerialId(49) val serviceMsgRemindType: Int = 0, @ProtoId(49) val serviceMsgRemindType: Int = 0,
@SerialId(50) val serviceMsgName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(50) val serviceMsgName: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(51) val vipType: Int = 0, @ProtoId(51) val vipType: Int = 0,
@SerialId(52) val vipLevel: Int = 0, @ProtoId(52) val vipLevel: Int = 0,
@SerialId(53) val pbPttWaveform: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(53) val pbPttWaveform: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(54) val userBigclubLevel: Int = 0, @ProtoId(54) val userBigclubLevel: Int = 0,
@SerialId(55) val userBigclubFlag: Int = 0, @ProtoId(55) val userBigclubFlag: Int = 0,
@SerialId(56) val nameplate: Int = 0, @ProtoId(56) val nameplate: Int = 0,
@SerialId(57) val autoReply: Int = 0, @ProtoId(57) val autoReply: Int = 0,
@SerialId(58) val reqIsBigclubHidden: Int = 0, @ProtoId(58) val reqIsBigclubHidden: Int = 0,
@SerialId(59) val showInMsgList: Int = 0, @ProtoId(59) val showInMsgList: Int = 0,
@SerialId(60) val oacMsgExtend: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(60) val oacMsgExtend: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(61) val groupMemberFlagEx2: Int = 0, @ProtoId(61) val groupMemberFlagEx2: Int = 0,
@SerialId(62) val groupRingtoneId: Int = 0, @ProtoId(62) val groupRingtoneId: Int = 0,
@SerialId(63) val robotGeneralTrans: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(63) val robotGeneralTrans: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(64) val troopPobingTemplate: Int = 0, @ProtoId(64) val troopPobingTemplate: Int = 0,
@SerialId(65) val hudongMark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(65) val hudongMark: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(66) val groupInfoFlagEx3: Int = 0 @ProtoId(66) val groupInfoFlagEx3: Int = 0
) : ProtoBuf ) : ProtoBuf
} }
@ -87,27 +87,27 @@ class Generalflags : ProtoBuf {
class ResvAttrForGiftMsg : ProtoBuf { class ResvAttrForGiftMsg : ProtoBuf {
@Serializable @Serializable
class ActivityGiftInfo( class ActivityGiftInfo(
@SerialId(1) val isActivityGift: Int = 0, @ProtoId(1) val isActivityGift: Int = 0,
@SerialId(2) val textColor: String = "", @ProtoId(2) val textColor: String = "",
@SerialId(3) val text: String = "", @ProtoId(3) val text: String = "",
@SerialId(4) val url: String = "" @ProtoId(4) val url: String = ""
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class InteractGift( class InteractGift(
@SerialId(1) val interactId: ByteArray = EMPTY_BYTE_ARRAY @ProtoId(1) val interactId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ResvAttr( class ResvAttr(
@SerialId(1) val int32SendScore: Int = 0, @ProtoId(1) val int32SendScore: Int = 0,
@SerialId(2) val int32RecvScore: Int = 0, @ProtoId(2) val int32RecvScore: Int = 0,
@SerialId(3) val charmHeroism: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val charmHeroism: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val buttonFlag: Int = 0, @ProtoId(4) val buttonFlag: Int = 0,
@SerialId(5) val objColor: Int = 0, @ProtoId(5) val objColor: Int = 0,
@SerialId(6) val animationType: Int = 0, @ProtoId(6) val animationType: Int = 0,
@SerialId(7) val msgInteractGift: ResvAttrForGiftMsg.InteractGift? = null, @ProtoId(7) val msgInteractGift: ResvAttrForGiftMsg.InteractGift? = null,
@SerialId(8) val activityGiftInfo: ResvAttrForGiftMsg.ActivityGiftInfo? = null @ProtoId(8) val activityGiftInfo: ResvAttrForGiftMsg.ActivityGiftInfo? = null
) : ProtoBuf ) : ProtoBuf
} }
@ -115,9 +115,9 @@ class ResvAttrForGiftMsg : ProtoBuf {
class SourceMsg : ProtoBuf { class SourceMsg : ProtoBuf {
@Serializable @Serializable
class ResvAttr( class ResvAttr(
@SerialId(1) val richMsg2: ByteArray? = null, @ProtoId(1) val richMsg2: ByteArray? = null,
@SerialId(2) val oriMsgtype: Int? = null, @ProtoId(2) val oriMsgtype: Int? = null,
@SerialId(3) val origUids: Long? = null // 原来是 list @ProtoId(3) val origUids: Long? = null // 原来是 list
) : ProtoBuf ) : ProtoBuf
} }
@ -125,17 +125,17 @@ class SourceMsg : ProtoBuf {
class VideoFile : ProtoBuf { class VideoFile : ProtoBuf {
@Serializable @Serializable
class ResvAttr( class ResvAttr(
@SerialId(1) val hotvideoIcon: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(1) val hotvideoIcon: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val hotvideoTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(2) val hotvideoTitle: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val hotvideoUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(3) val hotvideoUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val hotvideoIconSub: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(4) val hotvideoIconSub: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val specialVideoType: Int = 0, @ProtoId(5) val specialVideoType: Int = 0,
@SerialId(6) val dynamicText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(6) val dynamicText: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val msgTailType: Int = 0, @ProtoId(7) val msgTailType: Int = 0,
@SerialId(8) val redEnvelopeType: Int = 0, @ProtoId(8) val redEnvelopeType: Int = 0,
@SerialId(9) val shortVideoId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(9) val shortVideoId: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val animojiModelId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoId(10) val animojiModelId: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(11) val longVideoKandianType: Int = 0, @ProtoId(11) val longVideoKandianType: Int = 0,
@SerialId(12) val source: Int = 0 @ProtoId(12) val source: Int = 0
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -9,30 +9,30 @@
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
class StatSvcGetOnline { class StatSvcGetOnline {
@Serializable @Serializable
class Instance( class Instance(
@SerialId(1) val instanceId: Int = 0, @ProtoId(1) val instanceId: Int = 0,
@SerialId(2) val clientType: Int = 0 @ProtoId(2) val clientType: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class ReqBody( class ReqBody(
@SerialId(1) val uin: Long = 0L, @ProtoId(1) val uin: Long = 0L,
@SerialId(2) val appid: Int = 0 @ProtoId(2) val appid: Int = 0
) : ProtoBuf ) : ProtoBuf
@Serializable @Serializable
class RspBody( class RspBody(
@SerialId(1) val errorCode: Int = 0, @ProtoId(1) val errorCode: Int = 0,
@SerialId(2) val errorMsg: String = "", @ProtoId(2) val errorMsg: String = "",
@SerialId(3) val uin: Long = 0L, @ProtoId(3) val uin: Long = 0L,
@SerialId(4) val appid: Int = 0, @ProtoId(4) val appid: Int = 0,
@SerialId(5) val timeInterval: Int = 0, @ProtoId(5) val timeInterval: Int = 0,
@SerialId(6) val msgInstances: List<StatSvcGetOnline.Instance>? = null @ProtoId(6) val msgInstances: List<StatSvcGetOnline.Instance>? = null
) : ProtoBuf ) : ProtoBuf
} }

View File

@ -9,23 +9,23 @@
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 kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.random.Random import kotlin.random.Random
@Serializable @Serializable
class SyncCookie( class SyncCookie(
@SerialId(1) val time1: Long? = null, // 1580277992 @ProtoId(1) val time1: Long? = null, // 1580277992
@SerialId(2) val time: Long, // 1580277992 @ProtoId(2) val time: Long, // 1580277992
@SerialId(3) val unknown1: Long = Random.nextLong().absoluteValue,// 678328038 @ProtoId(3) val unknown1: Long = Random.nextLong().absoluteValue,// 678328038
@SerialId(4) val unknown2: Long = Random.nextLong().absoluteValue, // 1687142153 @ProtoId(4) val unknown2: Long = Random.nextLong().absoluteValue, // 1687142153
@SerialId(5) val const1: Long = const1_, // 1458467940 @ProtoId(5) val const1: Long = const1_, // 1458467940
@SerialId(11) val const2: Long = const2_, // 2683038258 @ProtoId(11) val const2: Long = const2_, // 2683038258
@SerialId(12) val unknown3: Long = 0x1d, @ProtoId(12) val unknown3: Long = 0x1d,
@SerialId(13) val lastSyncTime: Long? = null, @ProtoId(13) val lastSyncTime: Long? = null,
@SerialId(14) val unknown4: Long = 0 @ProtoId(14) val unknown4: Long = 0
) : ProtoBuf ) : ProtoBuf
private val const1_: Long = Random.nextLong().absoluteValue private val const1_: Long = Random.nextLong().absoluteValue

View File

@ -15,13 +15,11 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully 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.io.encryptAndWrite import net.mamoe.mirai.utils.io.encryptAndWrite
import net.mamoe.mirai.utils.io.writeShortLVByteArray import net.mamoe.mirai.utils.io.writeShortLVByteArray
/** @OptIn(ExperimentalUnsignedTypes::class)
* Encryption method to be used for packet body.
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
internal interface EncryptMethod { internal interface EncryptMethod {
val id: Int val id: Int
@ -33,16 +31,6 @@ internal interface EncryptMethodSessionKey : EncryptMethod {
val currentLoginState: Int val currentLoginState: Int
val sessionKey: ByteArray val sessionKey: ByteArray
/**
* buildPacket{
* byte 1
* byte if (currentLoginState == 2) 3 else 2
* fully key
* short 258
* short 0
* fully encrypted
* }
*/
override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket = override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket =
buildPacket { buildPacket {
require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" } require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" }
@ -65,29 +53,27 @@ inline class EncryptMethodSessionKeyLoginState3(override val sessionKey: ByteArr
override val currentLoginState: Int get() = 3 override val currentLoginState: Int get() = 3
} }
inline class EncryptMethodECDH135(override val ecdh: ECDH) : internal inline class EncryptMethodECDH135(override val ecdh: ECDH) :
EncryptMethodECDH { EncryptMethodECDH {
override val id: Int get() = 135 override val id: Int get() = 135
} }
inline class EncryptMethodECDH7(override val ecdh: ECDH) : internal inline class EncryptMethodECDH7(override val ecdh: ECDH) :
EncryptMethodECDH { EncryptMethodECDH {
override val id: Int get() = 7 override val id: Int get() = 7
} }
internal interface EncryptMethodECDH : EncryptMethod { internal interface EncryptMethodECDH : EncryptMethod {
companion object {
operator fun invoke(ecdh: ECDH): EncryptMethodECDH {
return if (ecdh.keyPair === ECDHKeyPair.DefaultStub) {
EncryptMethodECDH135(ecdh)
} else EncryptMethodECDH7(ecdh)
}
}
val ecdh: ECDH val ecdh: ECDH
/**
* **Packet Structure**
* byte 1
* byte 1
* byte[] [ECDH.privateKey]
* short 258
* short [ECDH.publicKey].size
* byte[] [ECDH.publicKey]
* byte[] encrypted `body()` by [ECDH.shareKey]
*/
override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket = override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket =
buildPacket { buildPacket {
writeByte(1) // const writeByte(1) // const
@ -95,15 +81,15 @@ internal interface EncryptMethodECDH : EncryptMethod {
writeFully(client.randomKey) writeFully(client.randomKey)
writeShort(258) // const writeShort(258) // const
// writeShortLVByteArray("04 CB 36 66 98 56 1E 93 6E 80 C1 57 E0 74 CA B1 3B 0B B6 8D DE B2 82 45 48 A1 B1 8D D4 FB 61 22 AF E1 2F E4 8C 52 66 D8 D7 26 9D 76 51 A8 EB 6F E7".hexToBytes()) if (ecdh.keyPair === ECDHKeyPair.DefaultStub) {
writeShortLVByteArray(ECDHKeyPair.DefaultStub.defaultPublicKey)
encryptAndWrite(ECDHKeyPair.DefaultStub.defaultShareKey, body)
} else {
writeShortLVByteArray(ecdh.keyPair.publicKey.getEncoded().drop(23).take(49).toByteArray().also { writeShortLVByteArray(ecdh.keyPair.publicKey.getEncoded().drop(23).take(49).toByteArray().also {
// it.toUHexString().debugPrint("PUBLIC KEY")
check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" } check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" }
//check(ecdh.calculateShareKeyByPeerPublicKey(it.adjustToPublicKey()).contentEquals(ecdh.keyPair.shareKey)) { "PublicKey Validation failed" }
}) })
// encryptAndWrite("26 33 BA EC 86 EB 79 E6 BC E0 20 06 5E A9 56 6C".hexToBytes(), body)
encryptAndWrite(ecdh.keyPair.initialShareKey, body) encryptAndWrite(ecdh.keyPair.initialShareKey, body)
} }
} }
}

View File

@ -34,39 +34,7 @@ internal class OutgoingPacket constructor(
internal val KEY_16_ZEROS = ByteArray(16) internal val KEY_16_ZEROS = ByteArray(16)
internal val EMPTY_BYTE_ARRAY = ByteArray(0) internal val EMPTY_BYTE_ARRAY = ByteArray(0)
/** @OptIn(MiraiInternalAPI::class)
* com.tencent.qphone.base.util.CodecWarpper#encodeRequest(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int, int, java.lang.String, byte, byte, byte, byte[], byte[], boolean)
*/
@Deprecated("危险", level = DeprecationLevel.ERROR)
@UseExperimental(MiraiInternalAPI::class)
internal inline fun OutgoingPacketFactory<*>.buildOutgoingPacket(
client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB?
name: String? = this.commandName,
commandName: String = this.commandName,
key: ByteArray = client.wLoginSigInfo.d2Key,
body: BytePacketBuilder.(sequenceId: Int) -> Unit
): OutgoingPacket {
val sequenceId: Int = client.nextSsoSequenceId()
return OutgoingPacket(name, commandName, sequenceId, buildPacket {
writeIntLVPacket(lengthOffset = { it + 4 }) {
writeInt(0x0B)
writeByte(bodyType)
writeInt(sequenceId)
writeByte(0)
client.uin.toString().let {
writeInt(it.length + 4)
writeStringUtf8(it)
}
encryptAndWrite(key) {
body(sequenceId)
}
}
})
}
@UseExperimental(MiraiInternalAPI::class)
internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket( internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
client: QQAndroidClient, client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB? bodyType: Byte = 1, // 1: PB?
@ -98,7 +66,7 @@ internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
} }
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket( internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
client: QQAndroidClient, client: QQAndroidClient,
bodyType: Byte = 1, // 1: PB? bodyType: Byte = 1, // 1: PB?
@ -128,7 +96,7 @@ internal inline fun IncomingPacketFactory<*>.buildResponseUniPacket(
}) })
} }
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeUniPacket( internal inline fun BytePacketBuilder.writeUniPacket(
commandName: String, commandName: String,
unknownData: ByteArray, unknownData: ByteArray,
@ -161,7 +129,7 @@ internal val NO_ENCRYPT: ByteArray = ByteArray(0)
/** /**
* com.tencent.qphone.base.util.CodecWarpper#encodeRequest(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int, int, java.lang.String, byte, byte, byte, byte[], byte[], boolean) * com.tencent.qphone.base.util.CodecWarpper#encodeRequest(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int, int, java.lang.String, byte, byte, byte, byte[], byte[], boolean)
*/ */
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket( internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket(
client: QQAndroidClient, client: QQAndroidClient,
bodyType: Byte, bodyType: Byte,
@ -199,7 +167,7 @@ internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket(
private inline val BRP_STUB get() = ByteReadPacket.Empty private inline val BRP_STUB get() = ByteReadPacket.Empty
@UseExperimental(MiraiInternalAPI::class) @OptIn(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeSsoPacket( internal inline fun BytePacketBuilder.writeSsoPacket(
client: QQAndroidClient, client: QQAndroidClient,
subAppId: Long, subAppId: Long,
@ -265,7 +233,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
writeIntLVPacket(lengthOffset = { it + 4 }, builder = body) writeIntLVPacket(lengthOffset = { it + 4 }, builder = body)
} }
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun BytePacketBuilder.writeOicqRequestPacket( internal fun BytePacketBuilder.writeOicqRequestPacket(
client: QQAndroidClient, client: QQAndroidClient,
encryptMethod: EncryptMethod, encryptMethod: EncryptMethod,

View File

@ -15,9 +15,10 @@ import kotlinx.io.core.toByteArray
import kotlinx.io.core.writeFully import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.protocol.LoginType import net.mamoe.mirai.qqandroid.network.protocol.LoginType
import net.mamoe.mirai.qqandroid.utils.NetworkType import net.mamoe.mirai.qqandroid.utils.NetworkType
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.MiraiPlatformUtils
import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.currentTimeMillis
import net.mamoe.mirai.utils.io.* import net.mamoe.mirai.utils.io.*
import net.mamoe.mirai.utils.md5
import kotlin.random.Random import kotlin.random.Random
/** /**
@ -76,6 +77,7 @@ fun BytePacketBuilder.t18(
} shouldEqualsTo 22 } shouldEqualsTo 22
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t106( fun BytePacketBuilder.t106(
appId: Long = 16L, appId: Long = 16L,
subAppId: Long = 537062845L, subAppId: Long = 537062845L,
@ -96,7 +98,7 @@ fun BytePacketBuilder.t106(
guid?.requireSize(16) guid?.requireSize(16)
writeShortLVPacket { writeShortLVPacket {
encryptAndWrite(md5(passwordMd5 + ByteArray(4) + (salt.takeIf { it != 0L } ?: uin).toInt().toByteArray())) { encryptAndWrite(MiraiPlatformUtils.md5(passwordMd5 + ByteArray(4) + (salt.takeIf { it != 0L } ?: uin).toInt().toByteArray())) {
writeShort(4)//TGTGTVer writeShort(4)//TGTGTVer
writeInt(Random.nextInt()) writeInt(Random.nextInt())
writeInt(5)//ssoVer writeInt(5)//ssoVer
@ -321,12 +323,13 @@ fun BytePacketBuilder.t144(
} }
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t109( fun BytePacketBuilder.t109(
androidId: ByteArray androidId: ByteArray
) { ) {
writeShort(0x109) writeShort(0x109)
writeShortLVPacket { writeShortLVPacket {
writeFully(md5(androidId)) writeFully(MiraiPlatformUtils.md5(androidId))
} shouldEqualsTo 16 } shouldEqualsTo 16
} }
@ -556,21 +559,23 @@ fun BytePacketBuilder.t400(
} }
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t187( fun BytePacketBuilder.t187(
macAddress: ByteArray macAddress: ByteArray
) { ) {
writeShort(0x187) writeShort(0x187)
writeShortLVPacket { writeShortLVPacket {
writeFully(md5(macAddress)) // may be md5 writeFully(MiraiPlatformUtils.md5(macAddress)) // may be md5
} }
} }
@OptIn(MiraiInternalAPI::class)
fun BytePacketBuilder.t188( fun BytePacketBuilder.t188(
androidId: ByteArray androidId: ByteArray
) { ) {
writeShort(0x188) writeShort(0x188)
writeShortLVPacket { writeShortLVPacket {
writeFully(md5(androidId)) writeFully(MiraiPlatformUtils.md5(androidId))
} shouldEqualsTo 16 } shouldEqualsTo 16
} }

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