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:
build:
@ -8,10 +8,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: setup-android
uses: msfjarvis/setup-android@0.2
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
# Gradle tasks to run - If you want to run ./gradlew assemble, specify assemble here.
gradleTasks: build -x mirai-core:jvmTest
java-version: 1.8
- 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
### mirai-core
@ -132,7 +326,7 @@ TIMPC
## `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 等监听模式.
**其他**

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.**
## UpdateLog
## Changelog
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
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
repositories{
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.
`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.
**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
On JVM: Java 6
On Android: SDK 15
### Using java
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.
#### Libraries used
Mirai uses these open-source libraries.
## Acknowledgements
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)
- [kotlinx-coroutines](https://github.com/Kotlin/kotlinx.coroutines)
@ -92,7 +101,19 @@ Mirai uses these open-source libraries.
- [javafx](https://github.com/openjdk/jfx)
- [kotlinx-serialization](https://github.com/Kotlin/kotlinx.serialization)
## License
## Acknowledgement
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)
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/>.

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)
[![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/)
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)**
多平台 **QQ Android 和 TimPC** 协议支持库与高效率的机器人框架.
多平台 **QQ Android****TIM PC** 协议支持库与高效率的机器人框架.
纯 Kotlin 实现协议和支持框架,模块<b>全部免费开源</b>
目前可运行在 JVM 或 Android。
Mirai既可以作为你项目中的QQ协议支持Lib, 也可以作为单独的Application与插件承载QQ机器人
目前可运行在 JVM 或 Android 平台
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
QQ for Android 8.2.0 版本2019 年 12 月)协议的实现,目前完成大部分。
- 高兼容性:协议仅含极少部分为硬编码,其余全部随官方方式动态生成
- 高安全性密匙随机ECDH 动态计算
- 已支持大部分使用场景, 详情请在[Project](https://github.com/mamoe/mirai/projects/3)查看
Mirai 目前为快速流转Moving fast状态, 增量版本之间可能不具有兼容性,任何功能都可能在没有警告的情况下添加、删除或者更改。
### mirai-core-timpc
TIM PC 2.3.2 版本2019 年 8 月)协议的实现,相较于 core仅新增少量 API. 详见 [README.md](mirai-core-timpc/)
支持的功能:
- 消息收发:图片文字复合消息,图片消息
- 群管功能:群员列表,禁言
(目前不再更新此协议,请关注上文的安卓协议)
Mirai 源码完全开放, 您可以参考 Mirai 的协议实现来开发其他框架, 但需注明来源并遵守开源协议要求 (AGPLv3)。
## Use directly
**直接使用 Mirai(终端环境/网页面板(将来)).**
[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动 Mirai 并获得机器人服务
本模块还未完善。
### 开发者
## Use as a library
**mirai-core 为独立设计, 可以作为库内置于任意 Java(JVM)/Android 项目中使用.**
**了解 mirai 架构** [Wiki](https://github.com/mamoe/mirai/wiki/Home)
### Gradle
Mirai 只发布在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库:
```kotlin
repositories{
jcenter()
}
```
若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。
**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 JVM 运行则只需要`-jvm`的依赖**
#### 使用 mirai 作为服务器,为 mirai 开发插件
请将 `VERSION` 替换为最新的版本(如 `0.13.0`):
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
**Mirai 目前还处于实验性阶段, 我们无法保证任何稳定性, API 也可能会随时修改.**
- (官方)`Java` 或 `Kotlin` 为 [mirai-console](https://github.com/mamoe/mirai-console) 直接编写插件并与其他插件开发者合作共享
- (社区)`C`, `C++` 等原生语言: [mirai-native](https://github.com/iTXTech/mirai-native) 支持酷Q插件在mirai上运行
- (社区)`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 核心由 API 模块(`mirai-core`)和协议模块组成。
只添加 API 模块将无法正常工作。
现在只推荐使用 TIMPC 协议,请参照下文选择对应目标平台的依赖添加。
#### 使用 mirai 为第三方依赖库引入项目
**common**
```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 内存
Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
## 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
### On JVM or Android
现在体验低付出高效率的 Mirai
```kotlin
val bot = TIMPC.Bot(qqId, password).alsoLogin()
bot.subscribeMessages {
"你好" reply "你好!"
"profile" reply { sender.queryProfile() }
contains("图片"){ File(imagePath).send() }
}
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) 示例和演示程序
[<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)
[<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/28707253?s=60&v=4" />](https://github.com/ryoii)
[<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)
[<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/18532671?s=60&v=4" />](https://github.com/uebian)
[<img width="60px" height="60px" src="https://avatars2.githubusercontent.com/u/10308687?s=60&v=4" />](https://github.com/Freedom0925)
[<img width="60px" height="60px" src="https://avatars3.githubusercontent.com/u/16398479?s=60&v=4" />](https://github.com/ice1000)
[<img width="60px" height="60px" src="https://avatars0.githubusercontent.com/u/20042607?s=60&v=4" />](https://github.com/PragmaTwice)
[<img width="60px" height="60px" src="https://avatars0.githubusercontent.com/u/25280943?s=60&v=4" />](https://github.com/HoshinoTented)
[<img width="60px" height="60px" src="https://avatars3.githubusercontent.com/u/40517459?s=60&v=4" />](https://github.com/Cyenoch)
## Build Requirements
## 鸣谢
- Kotlin 1.3.61
- JDK 8 (required)
- JDK 11for protocol tools, optional
- Android SDK 29 (for Android target, optional)
特别感谢 [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)
### 第三方类库(无排名)
#### Libraries used
感谢:
- [kotlin-stdlib](https://github.com/JetBrains/kotlin)
- [kotlinx-coroutines](https://github.com/Kotlin/kotlinx.coroutines)
- [kotlinx-io](https://github.com/Kotlin/kotlinx-io)
- [kotlin-reflect](https://github.com/JetBrains/kotlin)
- [pcap4j](https://github.com/kaitoy/pcap4j)
- [atomicfu](https://github.com/Kotlin/kotlinx.atomicfu)
- [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)
- [bouncycastle](https://www.bouncycastle.org/java.html)
## License
## 许可证
协议原版权归属腾讯科技股份有限公司所有,本项目其他代码遵守:
**GNU AFFERO GENERAL PUBLIC LICENSE version 3**
其中部分要求:
- (见 LICENSE 第 13 节) 尽管本许可协议有其他规定,但如果您修改本程序,则修改后的版本必须显着地为所有通过计算机网络与它进行远程交互的用户(如果您的版本支持这种交互)提供从网络服务器通过一些标准或惯用的软件复制方法**免费**访问相应的**源代码**的机会
- (见 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
kotlin.code.style=official
# config
mirai_version=0.15.0
mirai_japt_version=1.0.0
miraiVersion=0.27.0
kotlin.incremental.multiplatform=true
kotlin.parallel.tasks.in.project=true
# kotlin
kotlinVersion=1.3.61
kotlinVersion=1.3.70
# kotlin libraries
serializationVersion=0.14.0
coroutinesVersion=1.3.3
serializationVersion=0.20.0
coroutinesVersion=1.3.4
atomicFuVersion=0.14.1
kotlinXIoVersion=0.1.16
coroutinesIoVersion=0.1.16
# utility
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
// Source code from kotlinx.coroutines
def pomConfig = {
licenses {
@ -12,6 +13,7 @@ def pomConfig = {
developer {
id "mamoe"
name "Mamoe Technologies"
email "support@mamoe.net"
}
}
scm {
@ -65,12 +67,16 @@ bintrayUpload.dependsOn {
list
}
try{
// empty xxx-javadoc.jar
task javadocJar(type: Jar) {
archiveClassifier = 'javadoc'
}
task javadocJar(type: Jar) {
archiveClassifier = 'javadoc'
}
} catch (Exception e){
}
publishing {
publications.all {
// add empty javadocs (no need for MPP root publication which publishes only pom file)
@ -88,7 +94,7 @@ publishing {
it.artifactId = "$project.name-common"
break
case 'jvm':
it.artifactId = "$project.name"
it.artifactId = "$project.name-jvm"
break
case 'js':
case 'native':

View File

@ -1,5 +1,5 @@
#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
distributionPath=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
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-serialization")
`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 atomicFuVersion: String by rootProject.ext
@ -16,7 +16,7 @@ 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
@ -27,10 +27,12 @@ fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version"
description = "QQ protocol library"
version = rootProject.ext.get("mirai_version")!!.toString()
val isAndroidSDKAvailable: Boolean by project
val miraiVersion: String by project
version = miraiVersion
kotlin {
if (isAndroidSDKAvailable) {
apply(from = rootProject.file("gradle/android.gradle"))
@ -75,6 +77,7 @@ kotlin {
commonMain {
dependencies {
api(kotlinx("serialization-runtime-common", serializationVersion))
api(kotlinx("serialization-protobuf-common", serializationVersion))
}
}
commonTest {
@ -88,6 +91,7 @@ kotlin {
if (isAndroidSDKAvailable) {
val androidMain by getting {
dependencies {
api(kotlinx("serialization-protobuf", serializationVersion))
}
}
@ -105,6 +109,7 @@ kotlin {
dependencies {
runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE
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
*/
@Suppress("INAPPLICABLE_JVM_NAME")
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 {
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
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.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
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
actual constructor(
context: Context,
account: BotAccount,
configuration: BotConfiguration
) : 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.BotFactory
import net.mamoe.mirai.qqandroid.QQAndroid.Bot
import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.Context
import kotlin.jvm.JvmName
/**
* QQ for Android
*/
@Suppress("INAPPLICABLE_JVM_NAME")
expect object QQAndroid : BotFactory {
/**
* 使用指定的 [配置][configuration] 构造 [Bot] 实例
*/
@JvmName("newBot")
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
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.data.*
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
import net.mamoe.mirai.message.data.CustomFaceFromFile
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.MessageReceipt
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.postImage
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.utils.*
import net.mamoe.mirai.utils.io.toUHexString
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
import kotlin.coroutines.CoroutineContext
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(
private val jceFriendInfo: JceFriendInfo
) : FriendInfo {
@ -61,28 +53,33 @@ internal class QQImpl(
override val coroutineContext: CoroutineContext,
override val id: Long,
private val friendInfo: FriendInfo
) : ContactImpl(), QQ {
) : QQ() {
override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val nick: String
get() = friendInfo.nick
override suspend fun sendMessage(message: MessageChain) {
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> {
val event = FriendMessageSendEvent(this, message).broadcast()
if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent")
}
lateinit var source: MessageSource
bot.network.run {
check(
MessageSvc.PbSendMsg.ToFriend(
bot.client,
id,
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" }
}
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) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
}
@ -102,8 +99,9 @@ internal class QQImpl(
)
).sendAndExpect<LongConn.OffPicUp.Response>()
@Suppress("UNCHECKED_CAST") // bug
return when (response) {
is LongConn.OffPicUp.Response.FileExists -> NotOnlineImageFromFile(
is LongConn.OffPicUp.Response.FileExists -> OfflineFriendImage(
filepath = response.resourceId,
md5 = response.imageInfo.fileMd5,
fileLength = response.imageInfo.fileSize.toInt(),
@ -114,19 +112,27 @@ internal class QQImpl(
ImageUploadEvent.Succeed(this@QQImpl, image, it).broadcast()
}
is LongConn.OffPicUp.Response.RequireUpload -> {
Http.postImage("0x6ff0070", bot.uin, null, imageInput = image.input, inputSize = image.inputSize, uKeyHex = response.uKey.toUHexString(""))
// HighwayHelper.uploadImage(
// client = bot.client,
// serverIp = response.serverIp[0].toIpV4AddressString(),
// serverPort = response.serverPort[0],
// imageInput = image.input,
// inputSize = image.inputSize.toInt(),
// md5 = image.md5,
// uKey = response.uKey,
// commandId = 1
// )
MiraiPlatformUtils.Http.postImage(
"0x6ff0070",
bot.uin,
null,
imageInput = image.input,
inputSize = image.inputSize,
uKeyHex = response.uKey.toUHexString("")
)
//HighwayHelper.uploadImage(
// client = bot.client,
// serverIp = response.serverIp[0].toIpV4AddressString(),
// serverPort = response.serverPort[0],
// imageInput = image.input,
// inputSize = image.inputSize.toInt(),
// fileMd5 = image.md5,
// uKey = response.uKey,
// commandId = 1
//)
// 为什么不能 ??
return NotOnlineImageFromFile(
return OfflineFriendImage(
filepath = response.resourceId,
md5 = image.md5,
fileLength = image.inputSize.toInt(),
@ -144,7 +150,22 @@ internal class QQImpl(
}
}
} 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
@ -162,29 +183,54 @@ internal class QQImpl(
TODO("not implemented")
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
return other is QQ && other.id == this.id
}
override fun hashCode(): Int = super.hashCode()
override fun toString(): String = "QQ($id)"
}
@Suppress("MemberVisibilityCanBePrivate")
internal class MemberImpl(
qq: QQImpl,
val qq: QQImpl, // 不要 WeakRef
group: GroupImpl,
override val coroutineContext: CoroutineContext,
memberInfo: MemberInfo
) : ContactImpl(), Member, QQ by qq {
) : Member() {
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
@Suppress("PropertyName")
internal var _nameCard: String = memberInfo.nameCard
@Suppress("PropertyName")
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
get() = _nameCard
set(newValue) {
@ -220,7 +266,7 @@ internal class MemberImpl(
newValue
).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 {
if (this === other) return true
return other is Member && other.id == this.id
override fun hashCode(): Int {
var result = bot.hashCode()
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(
private val jceInfo: StTroopMemberInfo,
private val groupOwnerId: Long
jceInfo: StTroopMemberInfo,
groupOwnerId: Long
) : MemberInfo {
override val uin: Long get() = jceInfo.memberUin
override val nameCard: String get() = jceInfo.sName ?: ""
override val nick: String get() = jceInfo.nick
override val permission: MemberPermission
get() = when {
jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER
jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER
}
override val specialTitle: String get() = jceInfo.sSpecialTitle ?: ""
override val uin: Long = jceInfo.memberUin
override val nameCard: String = jceInfo.sName ?: ""
override val nick: String = jceInfo.nick
override val permission: MemberPermission = when {
jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER
jceInfo.dwFlag == 1L -> MemberPermission.ADMINISTRATOR
else -> MemberPermission.MEMBER
}
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")
@UseExperimental(MiraiInternalAPI::class)
@OptIn(MiraiInternalAPI::class)
internal class GroupImpl(
bot: QQAndroidBot, override val coroutineContext: CoroutineContext,
override val id: Long,
groupInfo: GroupInfo,
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()
val uin: Long = groupInfo.uin
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
var _botMuteRemaining: Int = groupInfo.botMuteRemaining
var _botMuteTimestamp: Int = groupInfo.botMuteRemaining
override val botMuteRemaining: Int =
if (_botMuteRemaining == 0 || _botMuteRemaining == 0xFFFFFFFF.toInt()) {
if (_botMuteTimestamp == 0 || _botMuteTimestamp == 0xFFFFFFFF.toInt()) {
0
} else {
_botMuteRemaining - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
_botMuteTimestamp - currentTimeSeconds.toInt() - bot.client.timeDifference.toInt()
}
override val members: ContactList<Member> = ContactList(members.mapNotNull {
if (it.uin == bot.uin) {
botPermission = it.permission
if (it.permission == MemberPermission.OWNER) {
owner = botAsMember
}
null
} else Member(it).also { member ->
if (member.permission == MemberPermission.OWNER) {
@ -344,11 +430,11 @@ internal class GroupImpl(
}.toLockFreeLinkedList())
internal var _name: String = groupInfo.name
internal var _announcement: String = groupInfo.memo
internal var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite
private var _announcement: String = groupInfo.memo
private var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite
internal var _confessTalk: Boolean = groupInfo.confessTalk
internal var _muteAll: Boolean = groupInfo.muteAll
internal var _autoApprove: Boolean = groupInfo.autoApprove
private var _autoApprove: Boolean = groupInfo.autoApprove
internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat
override var name: String
@ -414,12 +500,14 @@ internal class GroupImpl(
override var isAutoApproveEnabled: Boolean
get() = _autoApprove
@Suppress("UNUSED_PARAMETER")
set(newValue) {
TODO()
}
override var isAnonymousChatEnabled: Boolean
get() = _anonymousChat
@Suppress("UNUSED_PARAMETER")
set(newValue) {
TODO()
}
@ -465,15 +553,17 @@ internal class GroupImpl(
}
}
@MiraiExperimentalAPI
override suspend fun quit(): Boolean {
check(botPermission != MemberPermission.OWNER) { "An owner cannot quit from a owning group" }
TODO("not implemented")
}
@UseExperimental(MiraiExperimentalAPI::class)
@OptIn(MiraiExperimentalAPI::class)
override fun Member(memberInfo: MemberInfo): Member {
return MemberImpl(
bot.QQ(memberInfo) as QQImpl,
@OptIn(LowLevelAPI::class)
bot._lowLevelNewQQ(memberInfo) as QQImpl,
this,
this.coroutineContext,
memberInfo
@ -482,7 +572,8 @@ internal class GroupImpl(
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 {
@ -493,25 +584,31 @@ internal class GroupImpl(
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" }
val event = GroupMessageSendEvent(this, message).broadcast()
if (event.isCancelled) {
throw EventCancelledException("cancelled by FriendMessageSendEvent")
}
lateinit var source: MessageSourceFromSendGroup
bot.network.run {
val response = MessageSvc.PbSendMsg.ToGroup(
val response: MessageSvc.PbSendMsg.Response = MessageSvc.PbSendMsg.ToGroup(
bot.client,
id,
event.message
).sendAndExpect<MessageSvc.PbSendMsg.Response>()
) {
source = it
source.startWaitingSequenceId(this)
}.sendAndExpect()
check(
response is MessageSvc.PbSendMsg.Response.SUCCESS
) { "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) {
throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup")
}
@ -528,9 +625,11 @@ internal class GroupImpl(
filename = image.filename
).sendAndExpect()
@Suppress("UNCHECKED_CAST") // bug
when (response) {
is ImgStore.GroupPicUp.Response.Failed -> {
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}")
}
is ImgStore.GroupPicUp.Response.FileExists -> {
@ -546,22 +645,25 @@ internal class GroupImpl(
// fileId = response.fileId.toInt()
// )
// println("NMSL")
return CustomFaceFromFile(
return OfflineGroupImage(
md5 = image.md5,
filepath = resourceId
).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() }
}
is ImgStore.GroupPicUp.Response.RequireUpload -> {
HighwayHelper.uploadImage(
client = bot.client,
serverIp = response.uploadIpList.first().toIpV4AddressString(),
serverPort = response.uploadPortList.first(),
imageInput = image.input,
inputSize = image.inputSize.toInt(),
md5 = image.md5,
uKey = response.uKey,
commandId = 2
)
// 每 10KB 等 1 秒
withTimeoutOrNull(image.inputSize * 1000 / 1024 / 10) {
HighwayHelper.uploadImage(
client = bot.client,
serverIp = response.uploadIpList.first().toIpV4AddressString(),
serverPort = response.uploadPortList.first(),
imageInput = image.input,
inputSize = image.inputSize.toInt(),
fileMd5 = image.md5,
uKey = response.uKey,
commandId = 2
)
} ?: error("timeout uploading image: ${image.filename}")
val resourceId = image.calculateImageResourceId()
// return NotOnlineImageFromFile(
// resourceId = resourceId,
@ -573,7 +675,7 @@ internal class GroupImpl(
// imageType = image.imageType,
// fileId = response.fileId.toInt()
// )
return CustomFaceFromFile(
return OfflineGroupImage(
md5 = image.md5,
filepath = resourceId
).also { ImageUploadEvent.Succeed(this@GroupImpl, image, it).broadcast() }
@ -597,13 +699,24 @@ internal class GroupImpl(
}
}
} 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 {
@Suppress("DuplicatedCode", "DuplicatedCode")
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
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.BotImpl
import net.mamoe.mirai.contact.ContactList
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.filteringGetOrNull
import net.mamoe.mirai.LowLevelAPI
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.AddFriendResult
import net.mamoe.mirai.data.FriendInfo
import net.mamoe.mirai.data.GroupInfo
import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.event.broadcast
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.QQAndroidClient
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.list.FriendList
import net.mamoe.mirai.utils.*
import kotlin.collections.asSequence
import kotlin.coroutines.CoroutineContext
@UseExperimental(MiraiInternalAPI::class)
@OptIn(MiraiInternalAPI::class)
internal expect class QQAndroidBot constructor(
context: Context,
account: BotAccount,
configuration: BotConfiguration
) : QQAndroidBotBase
@UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class)
internal abstract class QQAndroidBotBase constructor(
context: Context,
account: BotAccount,
configuration: BotConfiguration
) : BotImpl<QQAndroidBotNetworkHandler>(account, configuration) {
) : BotImpl<QQAndroidBotNetworkHandler>(context, account, configuration) {
val client: QQAndroidClient =
QQAndroidClient(
context,
@ -53,17 +58,32 @@ internal abstract class QQAndroidBotBase constructor(
)
internal var firstLoginSucceed: Boolean = false
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 {
QQ(object : FriendInfo {
@OptIn(LowLevelAPI::class)
_lowLevelNewQQ(object : FriendInfo {
override val uin: Long get() = this@QQAndroidBotBase.uin
override val nick: String get() = this@QQAndroidBotBase.nick
})
}
override fun QQ(friendInfo: FriendInfo): QQ {
return QQImpl(this as QQAndroidBot, coroutineContext, friendInfo.uin, friendInfo)
@LowLevelAPI
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 {
@ -74,66 +94,138 @@ internal abstract class QQAndroidBotBase constructor(
// internally visible only
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? {
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 {
FriendList.GetTroopListSimplify(bot.client)
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
}.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(
client = bot.client,
groupCode = id
).sendAndExpect<GroupInfoImpl>()
groupCode = groupCode
).sendAndExpect<GroupInfoImpl>(retry = 2)
}
override suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence<MemberInfo> = network.run {
var nextUin = 0L
var sequence = sequenceOf<MemberInfoImpl>()
while (true) {
val data = FriendList.GetTroopMemberList(
client = bot.client,
targetGroupUin = groupUin,
targetGroupCode = groupCode,
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
sequence += data.members.asSequence().map { troopMemberInfo ->
MemberInfoImpl(troopMemberInfo, ownerId)
}
nextUin = data.nextUin
if (nextUin == 0L) {
break
@OptIn(LowLevelAPI::class)
override suspend fun _lowLevelQueryGroupMemberList(
groupUin: Long,
groupCode: Long,
ownerId: Long
): Sequence<MemberInfo> =
network.run {
var nextUin = 0L
var sequence = sequenceOf<MemberInfoImpl>()
while (true) {
val data = FriendList.GetTroopMemberList(
client = bot.client,
targetGroupUin = groupUin,
targetGroupCode = groupCode,
nextUin = nextUin
).sendAndExpect<FriendList.GetTroopMemberList.Response>(timeoutMillis = 3000)
sequence += data.members.asSequence().map { troopMemberInfo ->
MemberInfoImpl(troopMemberInfo, ownerId)
}
nextUin = data.nextUin
if (nextUin == 0L) {
break
}
}
return sequence
}
return sequence
}
override fun onEvent(event: BotEvent): Boolean {
return firstLoginSucceed
}
override suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult {
TODO("not implemented")
}
override suspend fun Image.download(): ByteReadPacket {
TODO("not implemented")
override suspend fun recall(source: MessageSource) {
if (source.senderId != uin && source.groupId != 0L) {
getGroup(source.groupId).checkBotPermissionOperator()
}
// println(source._miraiContentToString())
source.ensureSequenceIdAvailable()
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()
}
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.id}: $response" }
}
}
@Suppress("OverridingDeprecatedMember")
override suspend fun Image.downloadAsByteArray(): ByteArray {
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" }
}
}
override suspend fun approveFriendAddRequest(id: Long, remark: String?) {
TODO("not implemented")
@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.core.*
import kotlinx.serialization.*
import kotlinx.serialization.builtins.ByteArraySerializer
import kotlinx.serialization.builtins.MapEntrySerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.internal.*
import kotlinx.serialization.modules.EmptyModule
import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.JceStruct
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.toReadPacket
@PublishedApi
internal val CharsetGBK = Charset.forName("GBK")
@PublishedApi
internal val CharsetUTF8 = Charset.forName("UTF8")
@ -30,12 +53,15 @@ enum class JceCharset(val kotlinCharset: Charset) {
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]
*/
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 val count: Int,
@ -46,7 +72,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return 0
}
override fun endEncode(desc: SerialDescriptor) {
override fun endEncode(descriptor: SerialDescriptor) {
parentEncoder.writeHead(LIST, this.tag)
parentEncoder.encodeTaggedInt(0, count)
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())
}*/
override fun beginCollection(desc: SerialDescriptor, collectionSize: Int, vararg typeParams: KSerializer<*>): CompositeEncoder {
override fun beginCollection(
descriptor: SerialDescriptor,
collectionSize: Int,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder {
return this
}
override fun beginStructure(desc: SerialDescriptor, vararg typeParams: KSerializer<*>): CompositeEncoder {
override fun beginStructure(
descriptor: SerialDescriptor,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder {
return this
}
}
@ -81,11 +114,11 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
* From: com.qq.taf.jce.JceOutputStream
*/
@Suppress("unused", "MemberVisibilityCanBePrivate")
@UseExperimental(ExperimentalIoApi::class)
@OptIn(ExperimentalIoApi::class)
private open inner class JceEncoder(
internal val output: BytePacketBuilder
) : TaggedEncoder<Int>() {
override val context get() = this@Jce.context
override val context get() = this@JceOld.context
override fun SerialDescriptor.getTag(index: Int): Int {
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) {
StructureKind.LIST -> this
StructureKind.MAP -> this
StructureKind.CLASS, UnionKind.OBJECT -> this
is PolymorphicKind -> this
else -> throw SerializationException("Primitives are not supported at top-level")
}
@UseExperimental(ImplicitReflectionSerializer::class)
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when (serializer.descriptor) {
is MapLikeDescriptor -> {
val entries = (value as Map<*, *>).entries
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
this.writeHead(MAP, currentTag)
this.encodeTaggedInt(0, entries.count())
HashSetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries)
override fun beginStructure(
descriptor: SerialDescriptor,
vararg typeSerializers: KSerializer<*>
): CompositeEncoder =
when (descriptor.kind) {
StructureKind.LIST -> this
StructureKind.MAP -> this
StructureKind.CLASS, StructureKind.OBJECT -> this
is PolymorphicKind -> this
else -> throw SerializationException("Primitives are not supported at top-level")
}
ByteArraySerializer.descriptor -> encodeTaggedByteArray(popTag(), value as ByteArray)
is PrimitiveArrayDescriptor -> {
@OptIn(ImplicitReflectionSerializer::class)
@Suppress("UNCHECKED_CAST", "NAME_SHADOWING")
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
serializer.descriptor.kind == StructureKind.MAP -> {
try {
val entries = (value as Map<*, *>).entries
val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
this.writeHead(MAP, currentTag)
this.encodeTaggedInt(0, entries.count())
SetSerializer(mapEntrySerial).serialize(JceMapWriter(this.output), entries)
} catch (e: Exception) {
super.encodeSerializableValue(serializer, value)
}
}
serializer.descriptor.kind == StructureKind.LIST
&& value is ByteArray -> encodeTaggedByteArray(popTag(), value as ByteArray)
serializer.descriptor.kind == StructureKind.LIST
&& serializer.descriptor.getElementDescriptor(0) is PrimitiveKind -> {
serializer.serialize(
ListWriter(
when (value) {
@ -133,9 +176,8 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
value
)
}
is ArrayClassDesc -> {
val descriptor = serializer.descriptor as ReferenceArraySerializer<Any, Any?>
if (descriptor.typeParams.isNotEmpty() && descriptor.typeParams[0] is ByteSerializer) {
serializer.descriptor.kind == StructureKind.LIST && value is Array<*> -> {
if (serializer.descriptor.getElementDescriptor(0).kind is PrimitiveKind.BYTE) {
encodeTaggedByteArray(popTag(), (value as Array<Byte>).toByteArray())
} else
serializer.serialize(
@ -143,7 +185,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
value
)
}
is ListLikeDescriptor -> {
serializer.descriptor.kind == StructureKind.LIST -> {
serializer.serialize(
ListWriter((value as Collection<*>).size, popTag(), this),
value
@ -262,7 +304,8 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
is Double -> encodeTaggedDouble(tag, value)
is Boolean -> encodeTaggedBoolean(tag, value)
is String -> encodeTaggedString(tag, value)
is Unit -> encodeTaggedUnit(tag)
is Unit -> {
}
else -> error("unsupported type: ${value.getClassName()}")
}
}
@ -286,7 +329,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
val size: Int,
input: JceInput
) : JceDecoder(input) {
override fun decodeCollectionSize(desc: SerialDescriptor): Int {
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
return size
}
@ -300,7 +343,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
val size: Int,
input: JceInput
) : JceDecoder(input) {
override fun decodeCollectionSize(desc: SerialDescriptor): Int {
override fun decodeCollectionSize(descriptor: SerialDescriptor): Int {
return size
}
@ -312,7 +355,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
private open inner class JceStructReader(
input: JceInput
) : 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)
}
override fun decodeElementIndex(descriptor: SerialDescriptor): Int {
return 0
}
/**
* [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()}")
when (desc) {
when {
// 由于 Byte 的数组有两种方式写入, 需特定读取器
ByteArraySerializer.descriptor -> {
descriptor.kind == StructureKind.LIST
&& descriptor.getElementDescriptor(0).kind == PrimitiveKind.BYTE -> {
// ByteArray, 交给 decodeSerializableValue 进行处理
return this
}
is ListLikeDescriptor -> {
if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) {
// Array<Byte>
return this // 交给 decodeSerializableValue
}
descriptor.kind == StructureKind.LIST -> {
// if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) {
// // Array<Byte>
// return this // 交给 decodeSerializableValue
// }
val tag = currentTagOrNull
@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)
else -> error("type mismatch")
}
} == null && desc.isNullable) {
} == null && descriptor.isNullable) {
return NullReader(this.input)
}
}
is MapLikeDescriptor -> {
descriptor.kind == StructureKind.MAP -> {
val tag = currentTagOrNull
if (tag != null) {
popTag()
@ -391,7 +439,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return NullReader(this.input)
}
return super.beginStructure(desc, *typeParams)
return super.beginStructure(descriptor, *typeParams)
}
override fun decodeTaggedNull(tag: Int): Nothing? {
@ -410,7 +458,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
@Suppress("UNCHECKED_CAST")
override fun <T : Any> decodeNullableSerializableValue(deserializer: DeserializationStrategy<T?>): T? {
//
//println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}")
println("decodeNullableSerializableValue: ${deserializer::class.qualifiedName}")
if (deserializer is NullReader) {
return null
}
@ -419,13 +467,13 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return null
}
}
when (deserializer.descriptor) {
ByteArraySerializer.descriptor -> {
when {
deserializer.descriptor == ByteArraySerializer().descriptor -> {
val tag = popTag()
return if (isTagMissing(tag)) input.readByteArrayOrNull(tag) as? T
else input.readByteArray(tag) as T
}
is ListLikeDescriptor -> {
deserializer.descriptor.kind == StructureKind.LIST -> {
if (deserializer is ReferenceArraySerializer<*, *>
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams.isNotEmpty()
&& (deserializer as ListLikeSerializer<Any?, T, Any?>).typeParams[0] is ByteSerializer
@ -453,15 +501,17 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
}
error("UNREACHABLE CODE")
}
is MapLikeDescriptor -> {
deserializer.descriptor.kind == StructureKind.MAP -> {
val tag = popTag()
@Suppress("SENSELESS_COMPARISON")
if (input.skipToTagOrNull(tag) { head ->
check(head.type == MAP) { "type mismatch: ${head.type}" }
// 将 mapOf(k1 to v1, k2 to v2, ...) 转换为 listOf(k1, v1, k2, v2, ...) 以便于写入.
val serializer = (deserializer as MapLikeSerializer<Any?, Any?, T, *>)
val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
val setOfEntries = HashSetSerializer(mapEntrySerial).deserialize(JceMapReader(input.readInt(0), input))
val mapEntrySerial =
MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer)
val setOfEntries =
SetSerializer(mapEntrySerial).deserialize(JceMapReader(input.readInt(0), input))
return setOfEntries.associateBy({ it.key }, { it.value }) as T
} == null) {
if (isTagMissing(tag)) {
@ -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
if (tag != null) {
@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(
@PublishedApi
internal val input: ByteReadPacket,
@ -556,22 +606,38 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
}
tag = readUByte().toUInt()
}
currentJceHead = JceHead(tag = tag.toInt(), type = type.toByte())
currentJceHead = JceHead(
tag = tag.toInt(),
type = type.toByte()
)
// println("doReadHead: $currentJceHead")
return currentJceHead
}
fun readBoolean(tag: Int): Boolean = readBooleanOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByte(tag: Int): Byte = readByteOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readShort(tag: Int): Short = readShortOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readBoolean(tag: Int): Boolean =
readBooleanOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readByte(tag: Int): Byte =
readByteOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readShort(tag: Int): Short =
readShortOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readInt(tag: Int): Int = readIntOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readLong(tag: Int): Long = readLongOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readFloat(tag: Int): Float = readFloatOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readDouble(tag: Int): Double = readDoubleOrNull(tag) ?: error("cannot find tag $tag, currentJceHead=$currentJceHead")
fun readLong(tag: Int): Long =
readLongOrNull(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) {
when (it.type) {
@ -667,7 +733,7 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
} while (head.type.toInt() != 11)
}
@UseExperimental(ExperimentalUnsignedTypes::class)
@OptIn(ExperimentalUnsignedTypes::class)
@PublishedApi
internal fun skipField(type: Byte) = when (type.toInt()) {
0 -> this.input.discardExact(1)
@ -704,10 +770,10 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
@Suppress("MemberVisibilityCanBePrivate")
companion object {
val UTF8 = Jce(JceCharset.UTF8)
val GBK = Jce(JceCharset.GBK)
val UTF8 = JceOld(JceCharset.UTF8)
val GBK = JceOld(JceCharset.GBK)
fun byCharSet(c: JceCharset): Jce {
fun byCharSet(c: JceCharset): JceOld {
return if (c == JceCharset.UTF8) {
UTF8
} 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 =
(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 {
@ -742,14 +793,18 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo
return encoder.build()
}
override fun <T> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
return dumpAsPacket(serializer, obj).readBytes()
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
return dumpAsPacket(serializer, value).readBytes()
}
/**
* 注意 close [packet]!!
*/
fun <T> load(deserializer: DeserializationStrategy<T>, packet: ByteReadPacket, length: Int = packet.remaining.toInt()): T {
fun <T> load(
deserializer: DeserializationStrategy<T>,
packet: ByteReadPacket,
length: Int = packet.remaining.toInt()
): T {
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")
while (true) {
if (isEndOfInput) { // 读不了了
@ -793,32 +848,3 @@ internal inline fun <R> Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead)
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"
*/
@file:Suppress("DEPRECATION_ERROR")
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.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.SerialModule
import kotlinx.serialization.protobuf.ProtoBuf
@ -33,15 +41,19 @@ internal fun extractParameters(desc: SerialDescriptor, index: Int, zeroBasedDefa
*
* 代码复制自 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>() {
public override val context
internal open inner class ProtobufWriter(private val encoder: ProtobufEncoder) : TaggedEncoder<ProtoDesc>() {
override val 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.CLASS, UnionKind.OBJECT, is PolymorphicKind -> ObjectWriter(currentTagOrNull, encoder)
StructureKind.CLASS, StructureKind.OBJECT, is PolymorphicKind -> ObjectWriter(currentTagOrNull, encoder)
StructureKind.MAP -> MapRepeatedWriter(currentTagOrNull, encoder)
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")
override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when {
// 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 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)
}
}
@ -96,7 +111,7 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
val parentTag: ProtoDesc?, private val parentEncoder: ProtobufEncoder,
private val stream: ByteArrayOutputStream = ByteArrayOutputStream()
) : ProtobufWriter(ProtobufEncoder(stream)) {
override fun endEncode(desc: SerialDescriptor) {
override fun endEncode(descriptor: SerialDescriptor) {
if (parentTag != null) {
parentEncoder.writeBytes(stream.toByteArray(), parentTag.first)
} else {
@ -111,7 +126,8 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
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
}
@ -141,8 +157,9 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
out.write(content)
}
@OptIn(ExperimentalStdlibApi::class)
fun writeString(value: String, tag: Int) {
val bytes = value.toUtf8Bytes()
val bytes = value.encodeToByteArray()
writeBytes(bytes, tag)
}
@ -228,17 +245,17 @@ class ProtoBufWithNullableSupport(context: SerialModule = EmptyModule) : Abstrac
internal const val SIZE_DELIMITED = 2
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> load(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T = plain.load(deserializer, bytes)
override fun install(module: SerialModule) = throw IllegalStateException("You should not install anything to global instance")
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> dump(serializer: SerializationStrategy<T>, obj: T): ByteArray {
override fun <T> dump(serializer: SerializationStrategy<T>, value: T): ByteArray {
val encoder = ByteArrayOutputStream()
val dumper = ProtobufWriter(ProtobufEncoder(encoder))
dumper.encode(serializer, obj)
dumper.encode(serializer, value)
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
*/
@file:JvmName("SerializationUtils")
@file:JvmMultifileClass
package net.mamoe.mirai.qqandroid.io.serialization
import kotlinx.io.core.*
@ -15,20 +18,29 @@ import kotlinx.serialization.SerialDescriptor
import kotlinx.serialization.SerializationStrategy
import net.mamoe.mirai.qqandroid.io.JceStruct
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.RequestDataVersion3
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.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 {
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) {
this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct))
fun <T : JceStruct> BytePacketBuilder.writeJceStruct(
serializer: SerializationStrategy<T>,
struct: T,
charset: JceCharset = JceCharset.GBK
) {
Jce.byCharSet(charset).dumpTo(serializer, struct, this)
}
fun <T : JceStruct> ByteReadPacket.readJceStruct(
@ -36,7 +48,8 @@ fun <T : JceStruct> ByteReadPacket.readJceStruct(
charset: JceCharset = JceCharset.UTF8,
length: Int = this.remaining.toInt()
): 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 {
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()
3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.firstValue()
else -> error("unsupported version ${request.iVersion}")
} else when (request.iVersion.toInt()) {
2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.getOrElse(name) { error("cannot find $name") }.firstValue()
} else when (request.iVersion?.toInt() ?: 3) {
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") }
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) {
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
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.qqandroid.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgComm
import net.mamoe.mirai.utils.MiraiDebugAPI
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.io.discardExact
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.io.encodeToString
import net.mamoe.mirai.utils.io.hexToBytes
import net.mamoe.mirai.utils.io.read
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 {
val text = this.toString()
return ImMsgBody.Text(
str = this.toString(),
attr6Buf = AT_BUF_1 + this.target.toInt().toByteArray() + AT_BUF_2
str = text,
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(
filePath = this.filepath,
resId = this.resourceId,
@ -86,7 +93,17 @@ _400Height=0x000000EB(235)
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(
filePath = this.filepath,
fileId = this.fileId,
@ -189,30 +206,75 @@ notOnlineImage=NotOnlineImage#2050019814 {
private val atAllData = ImMsgBody.Elem(
text = ImMsgBody.Text(
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>()
if (this.any<QuoteReply>()) {
when (val source = this[QuoteReply].source) {
is MessageSourceFromServer -> elements.add(ImMsgBody.Elem(srcMsg = source.delegate))
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}")
}
}
this.forEach {
fun transformOneMessage(it: Message) {
when (it) {
is PlainText -> elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = it.stringValue)))
is At -> elements.add(ImMsgBody.Elem(text = it.toJceData()))
is CustomFaceFromFile -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is CustomFaceFromServer -> elements.add(ImMsgBody.Elem(customFace = it.delegate))
is NotOnlineImageFromServer -> elements.add(ImMsgBody.Elem(notOnlineImage = it.delegate))
is NotOnlineImageFromFile -> elements.add(ImMsgBody.Elem(notOnlineImage = it.toJceData()))
is At -> {
elements.add(ImMsgBody.Elem(text = it.toJceData()))
elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " ")))
}
is LightApp -> elements.add(
ImMsgBody.Elem(
lightApp = ImMsgBody.LightAppElem(
data = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is RichMessage -> elements.add(
ImMsgBody.Elem(
richMsg = ImMsgBody.RichMsg(
serviceId = when (it) {
is XmlMessage -> 60
is JsonMessage -> 1
else -> error("unsupported RichMessage")
},
template1 = byteArrayOf(1) + MiraiPlatformUtils.zip(it.content.toByteArray())
)
)
)
is OfflineGroupImage -> elements.add(ImMsgBody.Elem(customFace = it.toJceData()))
is 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 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 MessageSource -> {
@ -220,18 +282,20 @@ internal fun MessageChain.toRichTextElems(): MutableList<ImMsgBody.Elem> {
else -> error("unsupported message type: ${it::class.simpleName}")
}
}
this.forEach(::transformOneMessage)
// if(this.any<QuoteReply>()){
elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes())))
// }
if (this.any<RichMessage>()) {
// 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
}
internal class CustomFaceFromServer(
internal class OnlineGroupImageImpl(
internal val delegate: ImMsgBody.CustomFace
) : CustomFace() {
override val filepath: String get() = delegate.filePath
) : OnlineGroupImage() {
override val filepath: String = delegate.filePath
override val fileId: Int get() = delegate.fileId
override val serverIp: Int get() = delegate.serverIp
override val serverPort: Int get() = delegate.serverPort
@ -247,20 +311,22 @@ internal class CustomFaceFromServer(
override val size: Int get() = delegate.size
override val original: Int get() = delegate.origin
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 {
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 {
return filepath.hashCode() + 31 * md5.hashCode()
return imageId.hashCode() + 31 * md5.hashCode()
}
}
internal class NotOnlineImageFromServer(
internal class OnlineFriendImageImpl(
internal val delegate: ImMsgBody.NotOnlineImage
) : NotOnlineImage() {
) : OnlineFriendImage() {
override val resourceId: String get() = delegate.resId
override val md5: ByteArray get() = delegate.picMd5
override val filepath: String get() = delegate.filePath
@ -272,61 +338,98 @@ internal class NotOnlineImageFromServer(
override val downloadPath: String get() = delegate.downloadPath
override val fileId: Int get() = delegate.fileId
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 {
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 {
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 {
val elements = this.msgBody.richText.elems
val message = MessageChain(initialCapacity = elements.size + 1)
message.add(MessageSourceFromMsg(delegate = this))
elements.joinToMessageChain(message)
return message
return buildMessageChain(elements.size + 1) {
+MessageSourceFromMsg(delegate = this@toMessageChain)
elements.joinToMessageChain(this)
}.removeAtIfHasQuoteReply()
}
// These two functions are not the same.
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
// These two functions are not identical, dont combine.
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun ImMsgBody.SourceMsg.toMessageChain(): MessageChain {
val elements = this.elems!!
val message = MessageChain(initialCapacity = elements.size + 1)
message.add(MessageSourceFromServer(delegate = this))
elements.joinToMessageChain(message)
return message
return buildMessageChain(elements.size + 1) {
+MessageSourceFromServer(delegate = this@toMessageChain)
elements.joinToMessageChain(this)
}.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)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChain) {
@OptIn(
MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class, LowLevelAPI::class
)
internal fun List<ImMsgBody.Elem>.joinToMessageChain(message: MessageChainBuilder) {
this.forEach {
when {
it.srcMsg != null -> message.add(QuoteReply(MessageSourceFromServer(it.srcMsg)))
it.notOnlineImage != null -> message.add(NotOnlineImageFromServer(it.notOnlineImage))
it.customFace != null -> message.add(CustomFaceFromServer(it.customFace))
it.notOnlineImage != null -> message.add(OnlineFriendImageImpl(it.notOnlineImage))
it.customFace != null -> message.add(OnlineGroupImageImpl(it.customFace))
it.face != null -> message.add(Face(it.face.index))
it.text != null -> {
if (it.text.attr6Buf.isEmpty()) {
message.add(it.text.str.toMessage())
} else {
//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 05 01 00 00 00 00 00 00 all
// 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
it.text.attr6Buf.read {
discardExact(7)
id = readUInt().toLong()
}
if (id == 0L){
if (id == 0L) {
message.add(AtAll)
} 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 net.mamoe.mirai.data.MultiPacket
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.event.CancellableEvent
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.broadcast
import net.mamoe.mirai.event.*
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.WrongPasswordException
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.io.ByteArrayPool
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 kotlin.coroutines.CoroutineContext
import kotlin.jvm.Volatile
import kotlin.time.ExperimentalTime
@Suppress("MemberVisibilityCanBePrivate")
@UseExperimental(MiraiInternalAPI::class)
@OptIn(MiraiInternalAPI::class)
internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler() {
override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val supervisor: CompletableJob = SupervisorJob(bot.coroutineContext[Job])
@ -67,17 +65,20 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
private val packetReceiveLock: Mutex = Mutex()
private fun startPacketReceiverJobOrKill(cancelCause: CancellationException? = null): Job {
private suspend fun startPacketReceiverJobOrKill(cancelCause: CancellationException? = null): Job {
_packetReceiverJob?.cancel(cancelCause)
_packetReceiverJob?.join()
return this.launch(CoroutineName("Incoming Packet Receiver")) {
while (channel.isOpen) {
while (channel.isOpen && isActive) {
val rawInput = try {
channel.read()
} catch (e: CancellationException) {
return@launch
} catch (e: Throwable) {
BotOfflineEvent.Dropped(bot).broadcast()
if (this@QQAndroidBotNetworkHandler.isActive) {
bot.launch { BotOfflineEvent.Dropped(bot, e).broadcast() }
}
return@launch
}
packetReceiveLock.withLock {
@ -87,25 +88,42 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
}.also { _packetReceiverJob = it }
}
override suspend fun relogin() {
heartbeatJob?.cancel()
private fun startHeartbeatJobOrKill(cancelCause: CancellationException? = null): Job {
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.isOpen) {
kotlin.runCatching {
registerClientOnline()
registerClientOnline(500)
}.exceptionOrNull() ?: return
logger.info("Cannot do fast relogin. Trying slow relogin")
}
channel.close()
}
channel = PlatformSocket()
// TODO: 2020/2/14 连接多个服务器
// TODO: 2020/2/14 连接多个服务器, #52
withTimeoutOrNull(3000) {
channel.connect("113.96.13.208", 8080)
} ?: 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()
mainloop@ while (true) {
when (response) {
@ -116,21 +134,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
is WtLogin.Login.LoginPacketResponse.Captcha -> when (response) {
is WtLogin.Login.LoginPacketResponse.Captcha.Picture -> {
var result = response.data.withUse {
bot.configuration.loginSolver.onSolvePicCaptcha(bot, this)
}
var result = bot.configuration.loginSolver.onSolvePicCaptcha(bot, response.data)
if (result == null || result.length != 4) {
//refresh captcha
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
}
is WtLogin.Login.LoginPacketResponse.Captcha.Slider -> {
var ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url)
if (ticket == null) {
ticket = ""
}
val ticket = bot.configuration.loginSolver.onSolveSliderCaptcha(bot, response.url).orEmpty()
response = WtLogin.Login.SubCommand2.SubmitSliderCaptcha(bot.client, ticket).sendAndExpect()
continue@mainloop
}
@ -156,55 +170,73 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
// println("d2key=${bot.client.wLoginSigInfo.d2Key.toUHexString()}")
registerClientOnline()
startHeartbeatJobOrKill()
}
private suspend fun registerClientOnline() {
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>()
private suspend fun registerClientOnline(timeoutMillis: Long = 3000) {
StatSvc.Register(bot.client).sendAndExpect<StatSvc.Register.Response>(timeoutMillis)
}
// caches
private val _pendingEnabled = atomic(true)
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 {
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()
val friendListJob = launch {
try {
lateinit var loadFriends: suspend () -> Unit
// 不要用 fun, 不要 join declaration, 不要用 val, 编译失败警告
loadFriends = suspend loadFriends@{
logger.info("开始加载好友信息")
var currentFriendCount = 0
var totalFriendCount: Short
while (true) {
val data = FriendList.GetFriendGroupList(
bot.client,
currentFriendCount,
150,
0,
0
).sendAndExpect<FriendList.GetFriendGroupList.Response>(timeoutMillis = 5000, retry = 2)
val data = runCatching {
FriendList.GetFriendGroupList(
bot.client,
currentFriendCount,
150,
0,
0
).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
data.friendList.forEach {
// 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++
}
}
logger.verbose("正在加载好友列表 ${currentFriendCount}/${totalFriendCount}")
logger.verbose { "正在加载好友列表 ${currentFriendCount}/${totalFriendCount}" }
if (currentFriendCount >= totalFriendCount) {
break
}
// delay(200)
}
logger.info("好友列表加载完成, 共 ${currentFriendCount}")
} catch (e: Exception) {
logger.error("加载好友列表失败|一般这是由于加载过于频繁导致/将以热加载方式加载好友列表")
logger.info { "好友列表加载完成, 共 ${currentFriendCount}" }
}
loadFriends()
}
val groupJob = launch {
@ -214,15 +246,18 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
.sendAndExpect<FriendList.GetTroopListSimplify.Response>(retry = 2)
troopListData.groups.forEach { troopNum ->
launch {
try {
// 别用 fun, 别 val, 编译失败警告
lateinit var loadGroup: suspend () -> Unit
loadGroup = suspend {
tryNTimesOrException(3) {
bot.groups.delegate.addLast(
@Suppress("DuplicatedCode")
GroupImpl(
(GroupImpl(
bot = bot,
coroutineContext = bot.coroutineContext,
id = troopNum.groupCode,
groupInfo = bot.queryGroupInfo(troopNum.groupCode).apply {
groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply {
this as GroupInfoImpl
if (this.delegate.groupName == null) {
@ -239,51 +274,78 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
this.delegate.groupCode = troopNum.groupCode
},
members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin)
)
members = bot._lowLevelQueryGroupMemberList(
troopNum.groupUin,
troopNum.groupCode,
troopNum.dwGroupOwnerUin
)
))
)
} catch (e: Exception) {
logger.error("${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试")
logger.error(e)
}?.let {
logger.error { "${troopNum.groupCode}的列表拉取失败, 一段时间后将会重试" }
logger.error(it)
this@QQAndroidBotNetworkHandler.launch {
delay(10_000)
loadGroup()
}
}
Unit // 别删, 编译失败警告
}
launch {
loadGroup()
}
}
logger.info("群组列表与群成员加载完成, 共 ${troopListData.groups.size}")
logger.info { "群组列表与群成员加载完成, 共 ${troopListData.groups.size}" }
} catch (e: Exception) {
logger.error("加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表")
logger.error { "加载组信息失败|一般这是由于加载过于频繁导致/将以热加载方式加载群列表" }
logger.error(e)
}
}
joinAll(friendListJob, groupJob)
heartbeatJob = this@QQAndroidBotNetworkHandler.launch(CoroutineName("Heartbeat")) {
while (this.isActive) {
delay(bot.configuration.heartbeatPeriodMillis)
val failException = doHeartBeat()
if (failException != null) {
delay(bot.configuration.firstReconnectDelayMillis)
close()
BotOfflineEvent.Dropped(bot).broadcast()
withTimeoutOrNull(5000) {
lateinit var listener: Listener<PacketReceivedEvent>
listener = this.subscribeAlways {
if (it.packet is MessageSvc.PbGetMsg.GetMsgSuccess) {
listener.complete()
}
}
}
MessageSvc.PbGetMsg(bot.client, MsgSvc.SyncFlag.START, currentTimeSeconds).sendWithoutExpect()
} ?: error("timeout syncing friend message history")
bot.firstLoginSucceed = true
_pendingEnabled.value = false
pendingIncomingPackets?.forEach {
@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? {
val lastException: Exception?
try {
kotlin.runCatching {
Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
retry = 2
)
return null
}
Heartbeat.Alive(bot.client)
.sendAndExpect<Heartbeat.Alive.Response>(
timeoutMillis = bot.configuration.heartbeatTimeoutMillis,
@ -301,10 +363,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
*/
@Volatile
private var cachedPacketTimeoutJob: Job? = null
/**
* 缓存的包
*/
private val cachedPacket: AtomicRef<ByteReadPacket?> = atomic(null)
/**
* 缓存的包还差多少长度
*/
@ -316,10 +380,17 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
*
* @param input 一个完整的包的内容, 去掉开头的 int 包长度
*/
@UseExperimental(ExperimentalCoroutinesApi::class)
@OptIn(ExperimentalCoroutinesApi::class)
fun parsePacketAsync(input: Input): Job {
return this.launch(start = CoroutineStart.ATOMIC) {
input.use { parsePacket(it) }
return this.launch(
start = CoroutineStart.ATOMIC
) {
try {
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
private suspend inline fun <P : Packet> generifiedParsePacket(input: Input) {
KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
handlePacket(packetFactory, packet, commandName, sequenceId)
private suspend fun <P : Packet?> generifiedParsePacket(input: Input) {
KnownPacketFactories.parseIncomingPacket(
bot,
input
) { packetFactory: PacketFactory<P>, packet: P, commandName: String, sequenceId: Int ->
if (packet is MultiPacket<*>) {
packet.forEach {
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).
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 ->
if (listener.filter(commandName, sequenceId) && packetListeners.remove(listener)) {
listener.complete(packet)
}
}
// check top-level cancelling
if (PacketReceivedEvent(packet).broadcast().isCancelled) {
packetFactory?.run {
when (this) {
is OutgoingPacketFactory<P> -> bot.handle(packet)
is IncomingPacketFactory<P> -> bot.handle(packet, sequenceId)?.sendWithoutExpect()
}
}
if (packet != null && PacketReceivedEvent(packet).broadcast().isCancelled) {
return
}
// broadcast
if (packet is Event) {
if (packet is BroadcastControllable) {
if (packet.shouldBroadcast) packet.broadcast()
@ -372,15 +463,6 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
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) {
parsePacketAsync(rawInput.readPacket(length))
parsePacketAsync(rawInput.readPacketExact(length))
if (rawInput.remaining == 0L) {
cachedPacket.value = null // 表示包长度正好
@ -467,63 +549,77 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
* 发送一个包, 但不期待任何返回.
*/
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")) {
PacketLogger.debug { "Channel sending: $commandName" }
channel.send(delegate)
PacketLogger.debug { "Channel send done: $commandName" }
}
}
class TimeoutException(override val message: String?) : Exception()
/**
* 发送一个包, 并挂起直到接收到指定的返回包或超时(3000ms)
*
* @param retry 当不为 0 时将使用 [ByteArrayPool] 缓存. 因此若非必要, 请不要允许 retry
*/
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" }
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) {
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
packetListeners.addLast(handler)
try {
withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) {
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")
}
return doSendAndReceive(handler, delegate, 0) // no need
} finally {
packetListeners.remove(handler)
}
} else this.delegate.useBytes { data, length ->
repeat(retry + 1) {
return tryNTimes(retry + 1) {
val handler = PacketListener(commandName = commandName, sequenceId = sequenceId)
packetListeners.addLast(handler)
try {
withContext(this@QQAndroidBotNetworkHandler.coroutineContext + CoroutineName("Packet sender")) {
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
doSendAndReceive(handler, data, length)
} finally {
packetListeners.remove(handler)
}
}
throw lastException!!
}
}
@ -534,8 +630,9 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
internal inner class PacketListener( // callback
val commandName: String,
val sequenceId: Int
) : CompletableDeferred<Packet> by CompletableDeferred(supervisor) {
fun filter(commandName: String, sequenceId: Int) = this.commandName == commandName && this.sequenceId == sequenceId
) : CompletableDeferred<Packet?> by CompletableDeferred(supervisor) {
fun filter(commandName: String, sequenceId: Int) =
this.commandName == commandName && this.sequenceId == sequenceId
}
override fun close(cause: Throwable?) {
@ -545,5 +642,5 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler
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
*/
@file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.qqandroid.network
import kotlinx.atomicfu.AtomicInt
import kotlinx.atomicfu.atomic
import kotlinx.io.core.ByteReadPacket
import kotlinx.io.core.toByteArray
import kotlinx.io.core.*
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.RawAccountIdUse
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.PacketLogger
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.utils.SystemDeviceInfo
import net.mamoe.mirai.utils.*
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.*
/*
@ -41,7 +40,7 @@ import net.mamoe.mirai.utils.io.*
DOMAINS
Pskey: "openmobile.qq.com"
*/
@UseExperimental(MiraiExperimentalAPI::class, MiraiInternalAPI::class)
@OptIn(MiraiExperimentalAPI::class, MiraiInternalAPI::class)
@PublishedApi
internal open class QQAndroidClient(
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? {
keys.forEach { (key, value) ->
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
@ -101,8 +100,8 @@ internal open class QQAndroidClient(
var openAppId: Long = 715019303L
val apkVersionName: ByteArray get() = "8.2.0".toByteArray()
val buildVer: String get() = "8.2.0.1296"
val apkVersionName: ByteArray get() = "8.2.7".toByteArray()
val buildVer: String get() = "8.2.7.4410"
private val messageSequenceId: AtomicInt = atomic(22911)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
@ -113,7 +112,7 @@ internal open class QQAndroidClient(
private val highwayDataTransSequenceIdForGroup: AtomicInt = atomic(87017)
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)
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()
/**
* 协议版本?, 8.2.0 的为 8001
* 协议版本?, 8.2.7 的为 8001
*/
val protocolVersion: Short = 8001
@ -159,16 +158,18 @@ internal open class QQAndroidClient(
*/
val uin: Long get() = _uin
@UseExperimental(RawAccountIdUse::class)
@Suppress("PropertyName")
@OptIn(RawAccountIdUse::class)
@Suppress("PropertyName", "DEPRECATION_ERROR")
internal var _uin: Long = bot.account.id
var t530: ByteArray? = null
var t528: ByteArray? = null
/**
* t108 时更新
*/
var ksid: ByteArray = "|454001228437590|A8.2.0.27f6ea96".toByteArray()
var ksid: ByteArray = "|454001228437590|A8.2.7.27f6ea96".toByteArray()
/**
* t186
*/
@ -190,8 +191,9 @@ internal open class QQAndroidClient(
lateinit var t104: ByteArray
}
@OptIn(MiraiInternalAPI::class)
internal fun generateTgtgtKey(guid: ByteArray): ByteArray =
md5(getRandomByteArray(16) + guid)
MiraiPlatformUtils.md5(getRandomByteArray(16) + guid)
internal class ReserveUinInfo(
@ -314,6 +316,10 @@ internal class Pt4Token(data: ByteArray, creationTime: Long, expireTime: Long) :
internal typealias PSKeyMap = MutableMap<String, PSKey>
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) =
data.read {
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,108 +16,123 @@ import io.ktor.http.HttpStatusCode
import io.ktor.http.URLProtocol
import io.ktor.http.content.OutgoingContent
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.discardExact
import kotlinx.io.core.readAvailable
import kotlinx.io.core.use
import kotlinx.io.pool.useInstance
import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
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.copyAndClose
import net.mamoe.mirai.utils.io.ByteArrayPool
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")
internal suspend inline fun HttpClient.postImage(
internal suspend fun HttpClient.postImage(
htcmd: String,
uin: Long,
groupcode: Long?,
imageInput: Input,
imageInput: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor
inputSize: Long,
uKeyHex: String
): Boolean = try {
post<HttpStatusCode> {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
): Boolean = post<HttpStatusCode> {
url {
protocol = URLProtocol.HTTP
host = "htdata2.qq.com"
path("cgi-bin/httpconn")
parameters["htcmd"] = htcmd
parameters["uin"] = uin.toString()
parameters["htcmd"] = htcmd
parameters["uin"] = uin.toString()
if (groupcode != null) parameters["groupcode"] = groupcode.toString()
if (groupcode != null) parameters["groupcode"] = groupcode.toString()
parameters["term"] = "pc"
parameters["ver"] = "5603"
parameters["filesize"] = inputSize.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
parameters["term"] = "pc"
parameters["ver"] = "5603"
parameters["filesize"] = inputSize.toString()
parameters["range"] = 0.toString()
parameters["ukey"] = uKeyHex
userAgent("QQClient")
}
userAgent("QQClient")
}
body = object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize
body = object : OutgoingContent.WriteChannelContent() {
override val contentType: ContentType = ContentType.Image.Any
override val contentLength: Long = inputSize
override suspend fun writeTo(channel: io.ktor.utils.io.ByteWriteChannel) {
ByteArrayPool.useInstance { buffer: ByteArray ->
var size: Int
while (imageInput.readAvailable(buffer).also { size = it } != 0) {
channel.writeFully(buffer, 0, size)
override suspend fun writeTo(channel: ByteWriteChannel) {
ByteArrayPool.useInstance { buffer: ByteArray ->
when (imageInput) {
is Input -> {
var size: Int
while (imageInput.readAvailable(buffer).also { size = it } > 0) {
channel.writeFully(buffer, 0, size)
channel.flush()
}
}
is ByteReadChannel -> imageInput.copyAndClose(channel)
is InputStream -> {
var size: Int
while (imageInput.read(buffer).also { size = it } > 0) {
channel.writeFully(buffer, 0, size)
channel.flush()
}
}
else -> error("unsupported imageInput: ${imageInput::class.simpleName}")
}
}
}
} == HttpStatusCode.OK
} finally {
imageInput.close()
}
}
} == HttpStatusCode.OK
@UseExperimental(MiraiInternalAPI::class)
@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class)
internal object HighwayHelper {
@OptIn(InternalCoroutinesApi::class)
suspend fun uploadImage(
client: QQAndroidClient,
serverIp: String,
serverPort: Int,
uKey: ByteArray,
imageInput: Input,
imageInput: Any,
inputSize: Int,
md5: ByteArray,
fileMd5: ByteArray,
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(commandId == 2 || commandId == 1) { "bad commandId. Must be 1 or 2" }
val socket = PlatformSocket()
socket.connect(serverIp, serverPort)
socket.use {
socket.send(
Highway.RequestDataTrans(
uin = client.uin,
command = "PicUp.DataUp",
sequenceId =
if (commandId == 2) client.nextHighwayDataTransSequenceIdForGroup()
else client.nextHighwayDataTransSequenceIdForFriend(),
uKey = uKey,
data = imageInput,
dataSize = inputSize,
md5 = md5,
commandId = commandId
)
)
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
socket.read().withUse {
discardExact(1)
val headLength = readInt()
discardExact(4)
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
check(proto.errorCode == 0) { "image upload failed: Transfer errno=${proto.errorCode}" }
createImageDataPacketSequence(
client = client,
command = "PicUp.DataUp",
commandId = commandId,
uKey = uKey,
data = imageInput,
dataSize = inputSize,
fileMd5 = fileMd5
).collect {
socket.send(it)
//0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00
socket.read().withUse {
discardExact(1)
val headLength = readInt()
discardExact(4)
val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength)
check(proto.errorCode == 0) { "image upload failed: Transfer errno=${proto.errorCode}" }
}
}
}
}

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
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.Packet
import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
@Serializable
internal class BigDataChannel(
@SerialId(0) val vBigdataIplists: List<BigDataIpList>,
@SerialId(1) val sBigdataSigSession: ByteArray? = null,
@SerialId(2) val sBigdataKeySession: ByteArray? = null,
@SerialId(3) val uSigUin: Long? = null,
@SerialId(4) val iConnectFlag: Int? = 1,
@SerialId(5) val vBigdataPbBuf: ByteArray? = null
@JceId(0) val vBigdataIplists: List<BigDataIpList>,
@JceId(1) val sBigdataSigSession: ByteArray? = null,
@JceId(2) val sBigdataKeySession: ByteArray? = null,
@JceId(3) val uSigUin: Long? = null,
@JceId(4) val iConnectFlag: Int? = 1,
@JceId(5) val vBigdataPbBuf: ByteArray? = null
) : JceStruct
@Serializable
internal class BigDataIpInfo(
@SerialId(0) val uType: Long,
@SerialId(1) val sIp: String = "",
@SerialId(2) val uPort: Long
@JceId(0) val uType: Long,
@JceId(1) val sIp: String = "",
@JceId(2) val uPort: Long
) : JceStruct
@Serializable
internal class BigDataIpList(
@SerialId(0) val uServiceType: Long,
@SerialId(1) val vIplist: List<BigDataIpInfo>,
@SerialId(2) val netSegConfs: List<NetSegConf>? = null,
@SerialId(3) val ufragmentSize: Long? = null
@JceId(0) val uServiceType: Long,
@JceId(1) val vIplist: List<BigDataIpInfo>,
@JceId(2) val netSegConfs: List<NetSegConf>? = null,
@JceId(3) val ufragmentSize: Long? = null
) : JceStruct
@Serializable
internal class ClientLogConfig(
@SerialId(1) val type: Int,
@SerialId(2) val timeStart: TimeStamp? = null,
@SerialId(3) val timeFinish: TimeStamp? = null,
@SerialId(4) val loglevel: Byte? = null,
@SerialId(5) val cookie: Int? = null,
@SerialId(6) val lseq: Long? = null
@JceId(1) val type: Int,
@JceId(2) val timeStart: TimeStamp? = null,
@JceId(3) val timeFinish: TimeStamp? = null,
@JceId(4) val loglevel: Byte? = null,
@JceId(5) val cookie: Int? = null,
@JceId(6) val lseq: Long? = null
) : JceStruct
@Serializable
internal class DomainIpChannel(
@SerialId(0) val vDomainIplists: List<DomainIpList>
@JceId(0) val vDomainIplists: List<DomainIpList>
) : JceStruct
@Serializable
internal class DomainIpInfo(
@SerialId(1) val uIp: Int,
@SerialId(2) val uPort: Int
@JceId(1) val uIp: Int,
@JceId(2) val uPort: Int
) : JceStruct
@Serializable
internal class DomainIpList(
@SerialId(0) val uDomainType: Int,
@SerialId(1) val vIplist: List<DomainIpInfo>
@JceId(0) val uDomainType: Int,
@JceId(1) val vIplist: List<DomainIpInfo>
) : JceStruct
@Serializable
internal class FileStoragePushFSSvcList(
@SerialId(0) val vUpLoadList: List<FileStorageServerListInfo>,
@SerialId(1) val vPicDownLoadList: List<FileStorageServerListInfo>,
@SerialId(2) val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
@SerialId(3) val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
@SerialId(4) val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,
@SerialId(5) val bigDataChannel: BigDataChannel? = null,
@SerialId(6) val vVipEmotionList: List<FileStorageServerListInfo>? = null,
@SerialId(7) val vC2CPicDownList: List<FileStorageServerListInfo>? = null,
@SerialId(8) val fmtIPInfo: FmtIPInfo? = null,
@SerialId(9) val domainIpChannel: DomainIpChannel? = null,
@SerialId(10) val pttlist: ByteArray? = null
@JceId(0) val vUpLoadList: List<FileStorageServerListInfo>,
@JceId(1) val vPicDownLoadList: List<FileStorageServerListInfo>,
@JceId(2) val vGPicDownLoadList: List<FileStorageServerListInfo>? = null,
@JceId(3) val vQzoneProxyServiceList: List<FileStorageServerListInfo>? = null,
@JceId(4) val vUrlEncodeServiceList: List<FileStorageServerListInfo>? = null,
@JceId(5) val bigDataChannel: BigDataChannel? = null,
@JceId(6) val vVipEmotionList: List<FileStorageServerListInfo>? = null,
@JceId(7) val vC2CPicDownList: List<FileStorageServerListInfo>? = null,
@JceId(8) val fmtIPInfo: FmtIPInfo? = null,
@JceId(9) val domainIpChannel: DomainIpChannel? = null,
@JceId(10) val pttlist: ByteArray? = null
) : JceStruct
@Serializable
internal class FileStorageServerListInfo(
@SerialId(1) val sIP: String = "",
@SerialId(2) val iPort: Int
@JceId(1) val sIP: String = "",
@JceId(2) val iPort: Int
) : JceStruct
@Serializable
internal class FmtIPInfo(
@SerialId(0) val sGateIp: String = "",
@SerialId(1) val iGateIpOper: Long
@JceId(0) val sGateIp: String = "",
@JceId(1) val iGateIpOper: Long
) : JceStruct
@Serializable
internal class NetSegConf(
@SerialId(0) val uint32NetType: Long? = null,
@SerialId(1) val uint32Segsize: Long? = null,
@SerialId(2) val uint32Segnum: Long? = null,
@SerialId(3) val uint32Curconnnum: Long? = null
@JceId(0) val uint32NetType: Long? = null,
@JceId(1) val uint32Segsize: Long? = null,
@JceId(2) val uint32Segnum: Long? = null,
@JceId(3) val uint32Curconnnum: Long? = null
) : JceStruct
@Suppress("ArrayInDataClass")
@Serializable
internal data class PushReq(
@SerialId(1) val type: Int,
@SerialId(2) val jcebuf: ByteArray,
@SerialId(3) val seq: Long
@JceId(1) val type: Int,
@JceId(2) val jcebuf: ByteArray,
@JceId(3) val seq: Long
) : JceStruct, Packet
@Serializable
internal class PushResp(
@SerialId(1) val type: Int,
@SerialId(2) val seq: Long,
@SerialId(3) val jcebuf: ByteArray? = null
@JceId(1) val type: Int,
@JceId(2) val seq: Long,
@JceId(3) val jcebuf: ByteArray? = null
) : JceStruct
@Serializable
internal class SsoServerList(
@SerialId(1) val v2G3GList: List<SsoServerListInfo>,
@SerialId(3) val vWifiList: List<SsoServerListInfo>,
@SerialId(4) val iReconnect: Int,
@SerialId(5) val testSpeed: Byte? = null,
@SerialId(6) val useNewList: Byte? = null,
@SerialId(7) val iMultiConn: Int? = 1,
@SerialId(8) val vHttp2g3glist: List<SsoServerListInfo>? = null,
@SerialId(9) val vHttpWifilist: List<SsoServerListInfo>? = null
@JceId(1) val v2G3GList: List<SsoServerListInfo>,
@JceId(3) val vWifiList: List<SsoServerListInfo>,
@JceId(4) val iReconnect: Int,
@JceId(5) val testSpeed: Byte? = null,
@JceId(6) val useNewList: Byte? = null,
@JceId(7) val iMultiConn: Int? = 1,
@JceId(8) val vHttp2g3glist: List<SsoServerListInfo>? = null,
@JceId(9) val vHttpWifilist: List<SsoServerListInfo>? = null
) : JceStruct
@Serializable
internal class SsoServerListInfo(
@SerialId(1) val sIP: String = "",
@SerialId(2) val iPort: Int,
@SerialId(3) val linkType: Byte,
@SerialId(4) val proxy: Byte,
@SerialId(5) val protocolType: Byte? = null,
@SerialId(6) val iTimeOut: Int? = 10
@JceId(1) val sIP: String = "",
@JceId(2) val iPort: Int,
@JceId(3) val linkType: Byte,
@JceId(4) val proxy: Byte,
@JceId(5) val protocolType: Byte? = null,
@JceId(6) val iTimeOut: Int? = 10
) : JceStruct
@Serializable
internal class TimeStamp(
@SerialId(1) val year: Int,
@SerialId(2) val month: Byte,
@SerialId(3) val day: Byte,
@SerialId(4) val hour: Byte
@JceId(1) val year: Int,
@JceId(2) val month: Byte,
@JceId(3) val day: Byte,
@JceId(4) val hour: Byte
) : JceStruct

View File

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

View File

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

View File

@ -9,72 +9,72 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.data.Packet
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
@Suppress("ArrayInDataClass")
@Serializable
internal data class RequestPushNotify(
@SerialId(0) val uin: Long? = 0L,
@SerialId(1) val ctype: Byte = 0,
@SerialId(2) val strService: String?,
@SerialId(3) val strCmd: String?,
@SerialId(4) val vNotifyCookie: ByteArray? = EMPTY_BYTE_ARRAY,
@SerialId(5) val usMsgType: Int?,
@SerialId(6) val wUserActive: Int?,
@SerialId(7) val wGeneralFlag: Int?,
@SerialId(8) val bindedUin: Long?,
@SerialId(9) val stMsgInfo: MsgInfo?,
@SerialId(10) val msgCtrlBuf: String?,
@SerialId(11) val serverBuf: ByteArray?,
@SerialId(12) val pingFlag: Long?,
@SerialId(13) val svrip: Int?
@JceId(0) val uin: Long? = 0L,
@JceId(1) val ctype: Byte = 0,
@JceId(2) val strService: String?,
@JceId(3) val strCmd: String?,
@JceId(4) val vNotifyCookie: ByteArray? = EMPTY_BYTE_ARRAY,
@JceId(5) val usMsgType: Int?,
@JceId(6) val wUserActive: Int?,
@JceId(7) val wGeneralFlag: Int?,
@JceId(8) val bindedUin: Long?,
@JceId(9) val stMsgInfo: MsgInfo?,
@JceId(10) val msgCtrlBuf: String?,
@JceId(11) val serverBuf: ByteArray?,
@JceId(12) val pingFlag: Long?,
@JceId(13) val svrip: Int?
) : JceStruct, Packet
@Serializable
internal class MsgInfo(
@SerialId(0) val lFromUin: Long? = 0L,
@SerialId(1) val uMsgTime: Long? = 0L,
@SerialId(2) val shMsgType: Short,
@SerialId(3) val shMsgSeq: Short,
@SerialId(4) val strMsg: String?,
@SerialId(5) val uRealMsgTime: Int?,
@SerialId(6) val vMsg: ByteArray?,
@SerialId(7) val uAppShareID: Long?,
@SerialId(8) val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY,
@SerialId(9) val vAppShareCookie: ByteArray? = EMPTY_BYTE_ARRAY,
@SerialId(10) val lMsgUid: Long?,
@SerialId(11) val lLastChangeTime: Long?,
@SerialId(12) val vCPicInfo: List<CPicInfo>?,
@SerialId(13) val stShareData: ShareData?,
@SerialId(14) val lFromInstId: Long?,
@SerialId(15) val vRemarkOfSender: ByteArray?,
@SerialId(16) val strFromMobile: String?,
@SerialId(17) val strFromName: String?,
@SerialId(18) val vNickName: List<String>?//,
@JceId(0) val lFromUin: Long? = 0L,
@JceId(1) val uMsgTime: Long? = 0L,
@JceId(2) val shMsgType: Short,
@JceId(3) val shMsgSeq: Short,
@JceId(4) val strMsg: String?,
@JceId(5) val uRealMsgTime: Int?,
@JceId(6) val vMsg: ByteArray?,
@JceId(7) val uAppShareID: Long?,
@JceId(8) val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY,
@JceId(9) val vAppShareCookie: ByteArray? = EMPTY_BYTE_ARRAY,
@JceId(10) val lMsgUid: Long?,
@JceId(11) val lLastChangeTime: Long?,
@JceId(12) val vCPicInfo: List<CPicInfo>?,
@JceId(13) val stShareData: ShareData?,
@JceId(14) val lFromInstId: Long?,
@JceId(15) val vRemarkOfSender: ByteArray?,
@JceId(16) val strFromMobile: String?,
@JceId(17) val strFromName: String?,
@JceId(18) val vNickName: List<String>?//,
//@SerialId(19) val stC2CTmpMsgHead: TempMsgHead?
) : JceStruct
@Serializable
internal class ShareData(
@SerialId(0) val pkgname: String = "",
@SerialId(1) val msgtail: String = "",
@SerialId(2) val picurl: String = "",
@SerialId(3) val url: String = ""
@JceId(0) val pkgname: String = "",
@JceId(1) val msgtail: String = "",
@JceId(2) val picurl: String = "",
@JceId(3) val url: String = ""
) : JceStruct
@Serializable
internal class TempMsgHead(
@SerialId(0) val c2c_type: Int? = 0,
@SerialId(1) val serviceType: Int? = 0
@JceId(0) val c2c_type: Int? = 0,
@JceId(1) val serviceType: Int? = 0
) : JceStruct
@Serializable
internal class CPicInfo(
@SerialId(0) val vPath: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(1) val vHost: ByteArray? = EMPTY_BYTE_ARRAY
@JceId(0) val vPath: ByteArray = EMPTY_BYTE_ARRAY,
@JceId(1) val vHost: ByteArray? = EMPTY_BYTE_ARRAY
) : JceStruct

View File

@ -9,38 +9,38 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
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
private val EMPTY_MAP = mapOf<String, String>()
@Serializable
internal class RequestPacket(
@SerialId(1) val iVersion: Short = 3,
@SerialId(2) val cPacketType: Byte = 0,
@SerialId(3) val iMessageType: Int = 0,
@SerialId(4) val iRequestId: Int,
@SerialId(5) val sServantName: String = "",
@SerialId(6) val sFuncName: String = "",
@SerialId(7) val sBuffer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val iTimeout: Int? = 0,
@SerialId(9) val context: Map<String, String>? = EMPTY_MAP,
@SerialId(10) val status: Map<String, String>? = EMPTY_MAP
@JceId(1) val iVersion: Short? = 3,
@JceId(2) val cPacketType: Byte = 0,
@JceId(3) val iMessageType: Int = 0,
@JceId(4) val iRequestId: Int,
@JceId(5) val sServantName: String = "",
@JceId(6) val sFuncName: String = "",
@JceId(7) val sBuffer: ByteArray = EMPTY_BYTE_ARRAY,
@JceId(8) val iTimeout: Int? = 0,
@JceId(9) val context: Map<String, String>? = EMPTY_MAP,
@JceId(10) val status: Map<String, String>? = EMPTY_MAP
) : JceStruct
@Serializable
internal class RequestDataVersion3(
@SerialId(0) val map: Map<String, ByteArray> // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的
@JceId(0) val map: Map<String, ByteArray> // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的
) : JceStruct
@Serializable
internal class RequestDataVersion2(
@SerialId(0) val map: Map<String, Map<String, ByteArray>>
@JceId(0) val map: Map<String, Map<String, ByteArray>>
) : JceStruct
@Serializable
internal class RequestDataStructSvcReqRegister(
@SerialId(0) val struct: SvcReqRegister
@JceId(0) val struct: SvcReqRegister
) : JceStruct

View File

@ -9,14 +9,15 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
@Serializable
internal class RequestPushForceOffline(
@SerialId(0) val uin: Long,
@SerialId(1) val title: String? = "",
@SerialId(2) val tips: String? = "",
@SerialId(3) val sameDevice: Byte? = null
@JceId(0) val uin: Long,
@JceId(1) val title: String? = "",
@JceId(2) val tips: String? = "",
@JceId(3) val sameDevice: Byte? = null
) : JceStruct

View File

@ -9,47 +9,47 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.jce
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import net.mamoe.mirai.qqandroid.io.JceStruct
import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId
@Serializable
internal class SvcReqRegister(
@SerialId(0) val lUin: Long = 0L,
@SerialId(1) val lBid: Long = 0L,
@SerialId(2) val cConnType: Byte = 0,
@SerialId(3) val sOther: String = "",
@SerialId(4) val iStatus: Int = 11,
@SerialId(5) val bOnlinePush: Byte = 0,
@SerialId(6) val bIsOnline: Byte = 0,
@SerialId(7) val bIsShowOnline: Byte = 0,
@SerialId(8) val bKikPC: Byte = 0,
@SerialId(9) val bKikWeak: Byte = 0,
@SerialId(10) val timeStamp: Long = 0L,
@SerialId(11) val iOSVersion: Long = 0L,
@SerialId(12) val cNetType: Byte = 0,
@SerialId(13) val sBuildVer: String? = "",
@SerialId(14) val bRegType: Byte = 0,
@SerialId(15) val vecDevParam: ByteArray? = null,
@SerialId(16) val vecGuid: ByteArray? = null,
@SerialId(17) val iLocaleID: Int = 2052,
@SerialId(18) val bSlientPush: Byte = 0,
@SerialId(19) val strDevName: String? = null,
@SerialId(20) val strDevType: String? = null,
@SerialId(21) val strOSVer: String? = null,
@SerialId(22) val bOpenPush: Byte = 1,
@SerialId(23) val iLargeSeq: Long = 0L,
@SerialId(24) val iLastWatchStartTime: Long = 0L,
@SerialId(26) val uOldSSOIp: Long = 0L,
@SerialId(27) val uNewSSOIp: Long = 0L,
@SerialId(28) val sChannelNo: String? = null,
@SerialId(29) val lCpId: Long = 0L,
@SerialId(30) val strVendorName: String? = null,
@SerialId(31) val strVendorOSName: String? = null,
@SerialId(32) val strIOSIdfa: String? = null,
@SerialId(33) val bytes_0x769_reqbody: ByteArray? = null,
@SerialId(34) val bIsSetStatus: Byte = 0,
@SerialId(35) val vecServerBuf: ByteArray? = null,
@SerialId(36) val bSetMute: Byte = 0
@JceId(0) val lUin: Long = 0L,
@JceId(1) val lBid: Long = 0L,
@JceId(2) val cConnType: Byte = 0,
@JceId(3) val sOther: String = "",
@JceId(4) val iStatus: Int = 11,
@JceId(5) val bOnlinePush: Byte = 0,
@JceId(6) val bIsOnline: Byte = 0,
@JceId(7) val bIsShowOnline: Byte = 0,
@JceId(8) val bKikPC: Byte = 0,
@JceId(9) val bKikWeak: Byte = 0,
@JceId(10) val timeStamp: Long = 0L,
@JceId(11) val iOSVersion: Long = 0L,
@JceId(12) val cNetType: Byte = 0,
@JceId(13) val sBuildVer: String? = "",
@JceId(14) val bRegType: Byte = 0,
@JceId(15) val vecDevParam: ByteArray? = null,
@JceId(16) val vecGuid: ByteArray? = null,
@JceId(17) val iLocaleID: Int = 2052,
@JceId(18) val bSlientPush: Byte = 0,
@JceId(19) val strDevName: String? = null,
@JceId(20) val strDevType: String? = null,
@JceId(21) val strOSVer: String? = null,
@JceId(22) val bOpenPush: Byte = 1,
@JceId(23) val iLargeSeq: Long = 0L,
@JceId(24) val iLastWatchStartTime: Long = 0L,
@JceId(26) val uOldSSOIp: Long = 0L,
@JceId(27) val uNewSSOIp: Long = 0L,
@JceId(28) val sChannelNo: String? = null,
@JceId(29) val lCpId: Long = 0L,
@JceId(30) val strVendorName: String? = null,
@JceId(31) val strVendorOSName: String? = null,
@JceId(32) val strIOSIdfa: String? = null,
@JceId(33) val bytes_0x769_reqbody: ByteArray? = null,
@JceId(34) val bIsSetStatus: Byte = 0,
@JceId(35) val vecServerBuf: ByteArray? = null,
@JceId(36) val bSetMute: Byte = 0
// @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型
) : JceStruct

View File

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

View File

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

View File

@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
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 {
@Serializable
class DelImgReq(
@SerialId(1) val srcUin: Long = 0L,
@SerialId(2) val dstUin: Long = 0L,
@SerialId(3) val reqTerm: Int = 0,
@SerialId(4) val reqPlatformType: Int = 0,
@SerialId(5) val buType: Int = 0,
@SerialId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val picWidth: Int = 0,
@SerialId(9) val picHeight: Int = 0
@ProtoId(1) val srcUin: Long = 0L,
@ProtoId(2) val dstUin: Long = 0L,
@ProtoId(3) val reqTerm: Int = 0,
@ProtoId(4) val reqPlatformType: Int = 0,
@ProtoId(5) val buType: Int = 0,
@ProtoId(6) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(7) val fileResid: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(8) val picWidth: Int = 0,
@ProtoId(9) val picHeight: Int = 0
) : ProtoBuf
@Serializable
class DelImgRsp(
@SerialId(1) val result: Int = 0,
@SerialId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val result: Int = 0,
@ProtoId(2) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ExpRoamExtendInfo(
@SerialId(1) val resid: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val resid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ExpRoamPicInfo(
@SerialId(1) val shopFlag: Int = 0,
@SerialId(2) val pkgId: Int = 0,
@SerialId(3) val picId: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val shopFlag: Int = 0,
@ProtoId(2) val pkgId: Int = 0,
@ProtoId(3) val picId: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ExtensionCommPicTryUp(
@SerialId(1) val bytesExtinfo: List<ByteArray>? = null
@ProtoId(1) val bytesExtinfo: List<ByteArray>? = null
) : ProtoBuf
@Serializable
class ExtensionExpRoamTryUp(
@SerialId(1) val msgExproamPicInfo: List<ExpRoamPicInfo>? = null
@ProtoId(1) val msgExproamPicInfo: List<ExpRoamPicInfo>? = null
) : ProtoBuf
@Serializable
class GetImgUrlReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val dstUin: Long = 0L,
@SerialId(3) val fileid: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val urlFlag: Int = 0,
@SerialId(6) val urlType: Int = 0,
@SerialId(7) val reqTerm: Int = 0,
@SerialId(8) val reqPlatformType: Int = 0,
@SerialId(9) val innerIp: Int = 0,
@SerialId(10) val buType: Int = 0,
@SerialId(11) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val fileId: Long = 0L,
@SerialId(13) val fileSize: Long = 0L,
@SerialId(14) val originalPic: Int = 0,
@SerialId(15) val retryReq: Int = 0,
@SerialId(16) val fileHeight: Int = 0,
@SerialId(17) val fileWidth: Int = 0,
@SerialId(18) val picType: Int = 0,
@SerialId(19) val picUpTimestamp: Int = 0,
@SerialId(20) val reqTransferType: Int = 0
@ProtoId(1) val groupCode: Long = 0L,
@ProtoId(2) val dstUin: Long = 0L,
@ProtoId(3) val fileid: Long = 0L,
@ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val urlFlag: Int = 0,
@ProtoId(6) val urlType: Int = 0,
@ProtoId(7) val reqTerm: Int = 0,
@ProtoId(8) val reqPlatformType: Int = 0,
@ProtoId(9) val innerIp: Int = 0,
@ProtoId(10) val buType: Int = 0,
@ProtoId(11) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(12) val fileId: Long = 0L,
@ProtoId(13) val fileSize: Long = 0L,
@ProtoId(14) val originalPic: Int = 0,
@ProtoId(15) val retryReq: Int = 0,
@ProtoId(16) val fileHeight: Int = 0,
@ProtoId(17) val fileWidth: Int = 0,
@ProtoId(18) val picType: Int = 0,
@ProtoId(19) val picUpTimestamp: Int = 0,
@ProtoId(20) val reqTransferType: Int = 0
) : ProtoBuf
@Serializable
class GetImgUrlRsp(
@SerialId(1) val fileid: Long = 0L,
@SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val result: Int = 0,
@SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val msgImgInfo: ImgInfo? = null,
@SerialId(6) val bytesThumbDownUrl: List<ByteArray>? = null,
@SerialId(7) val bytesOriginalDownUrl: List<ByteArray>? = null,
@SerialId(8) val bytesBigDownUrl: List<ByteArray>? = null,
@SerialId(9) val uint32DownIp: List<Int>? = null,
@SerialId(10) val uint32DownPort: List<Int>? = null,
@SerialId(11) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(13) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(15) val fileId: Long = 0L,
@SerialId(16) val autoDownType: Int = 0,
@SerialId(17) val uint32OrderDownType: List<Int>? = null,
@SerialId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(20) val httpsUrlFlag: Int = 0,
@SerialId(26) val msgDownIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val fileid: Long = 0L,
@ProtoId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val result: Int = 0,
@ProtoId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val msgImgInfo: ImgInfo? = null,
@ProtoId(6) val bytesThumbDownUrl: List<ByteArray>? = null,
@ProtoId(7) val bytesOriginalDownUrl: List<ByteArray>? = null,
@ProtoId(8) val bytesBigDownUrl: List<ByteArray>? = null,
@ProtoId(9) val uint32DownIp: List<Int>? = null,
@ProtoId(10) val uint32DownPort: List<Int>? = null,
@ProtoId(11) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(12) val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(13) val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(14) val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(15) val fileId: Long = 0L,
@ProtoId(16) val autoDownType: Int = 0,
@ProtoId(17) val uint32OrderDownType: List<Int>? = null,
@ProtoId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(20) val httpsUrlFlag: Int = 0,
@ProtoId(26) val msgDownIp6: List<IPv6Info>? = null,
@ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class GetPttUrlReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val dstUin: Long = 0L,
@SerialId(3) val fileid: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val reqTerm: Int = 0,
@SerialId(6) val reqPlatformType: Int = 0,
@SerialId(7) val innerIp: Int = 0,
@SerialId(8) val buType: Int = 0,
@SerialId(9) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val fileId: Long = 0L,
@SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val codec: Int = 0,
@SerialId(13) val buId: Int = 0,
@SerialId(14) val reqTransferType: Int = 0,
@SerialId(15) val isAuto: Int = 0
@ProtoId(1) val groupCode: Long = 0L,
@ProtoId(2) val dstUin: Long = 0L,
@ProtoId(3) val fileid: Long = 0L,
@ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val reqTerm: Int = 0,
@ProtoId(6) val reqPlatformType: Int = 0,
@ProtoId(7) val innerIp: Int = 0,
@ProtoId(8) val buType: Int = 0,
@ProtoId(9) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(10) val fileId: Long = 0L,
@ProtoId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(12) val codec: Int = 0,
@ProtoId(13) val buId: Int = 0,
@ProtoId(14) val reqTransferType: Int = 0,
@ProtoId(15) val isAuto: Int = 0
) : ProtoBuf
@Serializable
class GetPttUrlRsp(
@SerialId(1) val fileid: Long = 0L,
@SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val result: Int = 0,
@SerialId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val bytesDownUrl: List<ByteArray>? = null,
@SerialId(6) val uint32DownIp: List<Int>? = null,
@SerialId(7) val uint32DownPort: List<Int>? = null,
@SerialId(8) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val downPara: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(10) val fileId: Long = 0L,
@SerialId(11) val transferType: Int = 0,
@SerialId(12) val allowRetry: Int = 0,
@SerialId(26) val msgDownIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(28) val strDomain: String = ""
@ProtoId(1) val fileid: Long = 0L,
@ProtoId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val result: Int = 0,
@ProtoId(4) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val bytesDownUrl: List<ByteArray>? = null,
@ProtoId(6) val uint32DownIp: List<Int>? = null,
@ProtoId(7) val uint32DownPort: List<Int>? = null,
@ProtoId(8) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(9) val downPara: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(10) val fileId: Long = 0L,
@ProtoId(11) val transferType: Int = 0,
@ProtoId(12) val allowRetry: Int = 0,
@ProtoId(26) val msgDownIp6: List<IPv6Info>? = null,
@ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(28) val strDomain: String = ""
) : ProtoBuf
@Suppress("ArrayInDataClass")
@Serializable
data class ImgInfo(
@SerialId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val fileType: Int = 0,
@SerialId(3) val fileSize: Long = 0L,
@SerialId(4) val fileWidth: Int = 0,
@SerialId(5) val fileHeight: Int = 0
@ProtoId(1) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val fileType: Int = 0,
@ProtoId(3) val fileSize: Long = 0L,
@ProtoId(4) val fileWidth: Int = 0,
@ProtoId(5) val fileHeight: Int = 0
) : ProtoBuf {
override fun toString(): String {
return "ImgInfo(fileMd5=${fileMd5.contentToString()}, fileType=$fileType, fileSize=$fileSize, fileWidth=$fileWidth, fileHeight=$fileHeight)"
@ -161,128 +161,128 @@ internal class Cmd0x388 : ProtoBuf {
@Serializable
class IPv6Info(
@SerialId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val port: Int = 0
@ProtoId(1) val ip6: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val port: Int = 0
) : ProtoBuf
@Serializable
class PicSize(
@SerialId(1) val original: Int = 0,
@SerialId(2) val thumb: Int = 0,
@SerialId(3) val high: Int = 0
@ProtoId(1) val original: Int = 0,
@ProtoId(2) val thumb: Int = 0,
@ProtoId(3) val high: Int = 0
) : ProtoBuf
@Serializable
class ReqBody(
@SerialId(1) val netType: Int = 0,
@SerialId(2) val subcmd: Int = 0,
@SerialId(3) val msgTryupImgReq: List<TryUpImgReq>? = null,
@SerialId(4) val msgGetimgUrlReq: List<GetImgUrlReq>? = null,
@SerialId(5) val msgTryupPttReq: List<TryUpPttReq>? = null,
@SerialId(6) val msgGetpttUrlReq: List<GetPttUrlReq>? = null,
@SerialId(7) val commandId: Int = 0,
@SerialId(8) val msgDelImgReq: List<DelImgReq>? = null,
@SerialId(1001) val extension: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val netType: Int = 0,
@ProtoId(2) val subcmd: Int = 0,
@ProtoId(3) val msgTryupImgReq: List<TryUpImgReq>? = null,
@ProtoId(4) val msgGetimgUrlReq: List<GetImgUrlReq>? = null,
@ProtoId(5) val msgTryupPttReq: List<TryUpPttReq>? = null,
@ProtoId(6) val msgGetpttUrlReq: List<GetPttUrlReq>? = null,
@ProtoId(7) val commandId: Int = 0,
@ProtoId(8) val msgDelImgReq: List<DelImgReq>? = null,
@ProtoId(1001) val extension: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class RspBody(
@SerialId(1) val clientIp: Int = 0,
@SerialId(2) val subcmd: Int = 0,
@SerialId(3) val msgTryupImgRsp: List<TryUpImgRsp>? = null,
@SerialId(4) val msgGetimgUrlRsp: List<GetImgUrlRsp>? = null,
@SerialId(5) val msgTryupPttRsp: List<TryUpPttRsp>? = null,
@SerialId(6) val msgGetpttUrlRsp: List<GetPttUrlRsp>? = null,
@SerialId(7) val msgDelImgRsp: List<DelImgRsp>? = null
@ProtoId(1) val clientIp: Int = 0,
@ProtoId(2) val subcmd: Int = 0,
@ProtoId(3) val msgTryupImgRsp: List<TryUpImgRsp>? = null,
@ProtoId(4) val msgGetimgUrlRsp: List<GetImgUrlRsp>? = null,
@ProtoId(5) val msgTryupPttRsp: List<TryUpPttRsp>? = null,
@ProtoId(6) val msgGetpttUrlRsp: List<GetPttUrlRsp>? = null,
@ProtoId(7) val msgDelImgRsp: List<DelImgRsp>? = null
) : ProtoBuf
@Serializable
class TryUpImgReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val srcUin: Long = 0L,
@SerialId(3) val fileId: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileSize: Long = 0L,
@SerialId(6) val fileName: String ="",
@SerialId(7) val srcTerm: Int = 0,
@SerialId(8) val platformType: Int = 0,
@SerialId(9) val buType: Int = 0,
@SerialId(10) val picWidth: Int = 0,
@SerialId(11) val picHeight: Int = 0,
@SerialId(12) val picType: Int = 0,
@SerialId(13) val buildVer: String = "",
@SerialId(14) val innerIp: Int = 0,
@SerialId(15) val appPicType: Int = 0,
@SerialId(16) val originalPic: Int = 0,
@SerialId(17) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(18) val dstUin: Long = 0L,
@SerialId(19) val srvUpload: Int = 0,
@SerialId(20) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val groupCode: Long = 0L,
@ProtoId(2) val srcUin: Long = 0L,
@ProtoId(3) val fileId: Long = 0L,
@ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val fileSize: Long = 0L,
@ProtoId(6) val fileName: String = "",
@ProtoId(7) val srcTerm: Int = 0,
@ProtoId(8) val platformType: Int = 0,
@ProtoId(9) val buType: Int = 0,
@ProtoId(10) val picWidth: Int = 0,
@ProtoId(11) val picHeight: Int = 0,
@ProtoId(12) val picType: Int = 0,
@ProtoId(13) val buildVer: String = "",
@ProtoId(14) val innerIp: Int = 0,
@ProtoId(15) val appPicType: Int = 0,
@ProtoId(16) val originalPic: Int = 0,
@ProtoId(17) val fileIndex: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(18) val dstUin: Long = 0L,
@ProtoId(19) val srvUpload: Int = 0,
@ProtoId(20) val transferUrl: ByteArray = EMPTY_BYTE_ARRAY
) : ImgReq
@Serializable
class TryUpImgRsp(
@SerialId(1) val fileId: Long = 0L,
@SerialId(2) val result: Int = 0,
@SerialId(3) val failMsg: String = "",
@SerialId(4) val boolFileExit: Boolean = false,
@SerialId(5) val msgImgInfo: ImgInfo? = null,
@SerialId(6) val uint32UpIp: List<Int>? = null,
@SerialId(7) val uint32UpPort: List<Int>? = null,
@SerialId(8) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(9) val fileid: Long = 0L,
@SerialId(10) val upOffset: Long = 0L,
@SerialId(11) val blockSize: Long = 0L,
@SerialId(12) val boolNewBigChan: Boolean = false,
@SerialId(26) val msgUpIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(1001) val msgInfo4busi: TryUpInfo4Busi? = null
@ProtoId(1) val fileId: Long = 0L,
@ProtoId(2) val result: Int = 0,
@ProtoId(3) val failMsg: String = "",
@ProtoId(4) val boolFileExit: Boolean = false,
@ProtoId(5) val msgImgInfo: ImgInfo? = null,
@ProtoId(6) val uint32UpIp: List<Int>? = null,
@ProtoId(7) val uint32UpPort: List<Int>? = null,
@ProtoId(8) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(9) val fileid: Long = 0L,
@ProtoId(10) val upOffset: Long = 0L,
@ProtoId(11) val blockSize: Long = 0L,
@ProtoId(12) val boolNewBigChan: Boolean = false,
@ProtoId(26) val msgUpIp6: List<IPv6Info>? = null,
@ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(1001) val msgInfo4busi: TryUpInfo4Busi? = null
) : ProtoBuf
@Serializable
class TryUpInfo4Busi(
@SerialId(1) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val downDomain: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val fileResid: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class TryUpPttReq(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val srcUin: Long = 0L,
@SerialId(3) val fileId: Long = 0L,
@SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val fileSize: Long = 0L,
@SerialId(6) val fileName: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(7) val srcTerm: Int = 0,
@SerialId(8) val platformType: Int = 0,
@SerialId(9) val buType: Int = 0,
@SerialId(10) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(11) val innerIp: Int = 0,
@SerialId(12) val voiceLength: Int = 0,
@SerialId(13) val boolNewUpChan: Boolean = false,
@SerialId(14) val codec: Int = 0,
@SerialId(15) val voiceType: Int = 0,
@SerialId(16) val buId: Int = 0
@ProtoId(1) val groupCode: Long = 0L,
@ProtoId(2) val srcUin: Long = 0L,
@ProtoId(3) val fileId: Long = 0L,
@ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val fileSize: Long = 0L,
@ProtoId(6) val fileName: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(7) val srcTerm: Int = 0,
@ProtoId(8) val platformType: Int = 0,
@ProtoId(9) val buType: Int = 0,
@ProtoId(10) val buildVer: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(11) val innerIp: Int = 0,
@ProtoId(12) val voiceLength: Int = 0,
@ProtoId(13) val boolNewUpChan: Boolean = false,
@ProtoId(14) val codec: Int = 0,
@ProtoId(15) val voiceType: Int = 0,
@ProtoId(16) val buId: Int = 0
) : ProtoBuf
@Serializable
class TryUpPttRsp(
@SerialId(1) val fileId: Long = 0L,
@SerialId(2) val result: Int = 0,
@SerialId(3) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val boolFileExit: Boolean = false,
@SerialId(5) val uint32UpIp: List<Int>? = null,
@SerialId(6) val uint32UpPort: List<Int>? = null,
@SerialId(7) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(8) val fileid: Long = 0L,
@SerialId(9) val upOffset: Long = 0L,
@SerialId(10) val blockSize: Long = 0L,
@SerialId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(12) val channelType: Int = 0,
@SerialId(26) val msgUpIp6: List<IPv6Info>? = null,
@SerialId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val fileId: Long = 0L,
@ProtoId(2) val result: Int = 0,
@ProtoId(3) val failMsg: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val boolFileExit: Boolean = false,
@ProtoId(5) val uint32UpIp: List<Int>? = null,
@ProtoId(6) val uint32UpPort: List<Int>? = null,
@ProtoId(7) val upUkey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(8) val fileid: Long = 0L,
@ProtoId(9) val upOffset: Long = 0L,
@ProtoId(10) val blockSize: Long = 0L,
@ProtoId(11) val fileKey: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(12) val channelType: Int = 0,
@ProtoId(26) val msgUpIp6: List<IPv6Info>? = null,
@ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY
) : 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
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
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 {
@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
@ProtoId(1) val friUin: Long = 0L,
@ProtoId(2) val friNick: String = "",
@ProtoId(3) val time: Long = 0L,
@ProtoId(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
@ProtoId(1) val id: Int = 0,
@ProtoId(2) val type: Int = 0,
@ProtoId(4) val seq: Long = 0,
@ProtoId(5) val name: String = "",
@ProtoId(6) val newflag: Int = 0,
@ProtoId(7) val time: Long = 0L,
@ProtoId(8) val msgBindFri: Common.BindInfo? = null,
@ProtoId(11) val desc: String = "",
@ProtoId(31) val level: Int = 0,
@ProtoId(36) val taskinfos: List<Common.MedalTaskInfo>? = null,
@ProtoId(40) val point: Int = 0,
@ProtoId(41) val pointLevel2: Int = 0,
@ProtoId(42) val pointLevel3: Int = 0,
@ProtoId(43) val seqLevel2: Long = 0,
@ProtoId(44) val seqLevel3: Long = 0,
@ProtoId(45) val timeLevel2: Long = 0L,
@ProtoId(46) val timeLevel3: Long = 0L,
@ProtoId(47) val descLevel2: String = "",
@ProtoId(48) val descLevel3: String = "",
@ProtoId(49) val endtime: Int = 0,
@ProtoId(50) val detailUrl: String = "",
@ProtoId(51) val detailUrl2: String = "",
@ProtoId(52) val detailUrl3: String = "",
@ProtoId(53) val taskDesc: String = "",
@ProtoId(54) val taskDesc2: String = "",
@ProtoId(55) val taskDesc3: String = "",
@ProtoId(56) val levelCount: Int = 0,
@ProtoId(57) val noProgress: Int = 0,
@ProtoId(58) val resource: String = "",
@ProtoId(59) val fromuinLevel: Int = 0,
@ProtoId(60) val unread: Int = 0,
@ProtoId(61) val unread2: Int = 0,
@ProtoId(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
@ProtoId(1) val taskid: Int = 0,
@ProtoId(32) val int32TaskValue: Int = 0,
@ProtoId(33) val tarValue: Int = 0,
@ProtoId(34) val tarValueLevel2: Int = 0,
@ProtoId(35) val tarValueLevel3: Int = 0
) : ProtoBuf
}
@ -75,475 +75,475 @@ class Common : ProtoBuf {
class AppointDefine : ProtoBuf {
@Serializable
class ADFeedContent(
@SerialId(1) val msgUserInfo: AppointDefine.UserInfo? = null,
@SerialId(2) val strPicUrl: List<String> = listOf(),
@SerialId(3) val msgText: AppointDefine.RichText? = null,
@SerialId(4) val attendInfo: String = "",
@SerialId(5) val actionUrl: String = "",
@SerialId(6) val publishTime: Int = 0,
@SerialId(7) val msgHotTopicList: AppointDefine.HotTopicList? = null,
@SerialId(8) val moreUrl: String = "",
@SerialId(9) val recordDuration: String = ""
@ProtoId(1) val msgUserInfo: AppointDefine.UserInfo? = null,
@ProtoId(2) val strPicUrl: List<String> = listOf(),
@ProtoId(3) val msgText: AppointDefine.RichText? = null,
@ProtoId(4) val attendInfo: String = "",
@ProtoId(5) val actionUrl: String = "",
@ProtoId(6) val publishTime: Int = 0,
@ProtoId(7) val msgHotTopicList: AppointDefine.HotTopicList? = null,
@ProtoId(8) val moreUrl: String = "",
@ProtoId(9) val recordDuration: String = ""
) : ProtoBuf
@Serializable
class RichText(
@SerialId(1) val msgElems: List<AppointDefine.Elem>? = null
@ProtoId(1) val msgElems: List<AppointDefine.Elem>? = null
) : ProtoBuf
@Serializable
class RankEvent(
@SerialId(1) val listtype: Int = 0,
@SerialId(2) val notifytype: Int = 0,
@SerialId(3) val eventtime: Int = 0,
@SerialId(4) val seq: Int = 0,
@SerialId(5) val notifyTips: String = ""
@ProtoId(1) val listtype: Int = 0,
@ProtoId(2) val notifytype: Int = 0,
@ProtoId(3) val eventtime: Int = 0,
@ProtoId(4) val seq: Int = 0,
@ProtoId(5) val notifyTips: String = ""
) : ProtoBuf
@Serializable
class Wifi(
@SerialId(1) val mac: Long = 0L,
@SerialId(2) val int32Rssi: Int = 0
@ProtoId(1) val mac: Long = 0L,
@ProtoId(2) val int32Rssi: Int = 0
) : ProtoBuf
@Serializable
class InterestItem(
@SerialId(1) val tagId: Long = 0L,
@SerialId(2) val tagName: String = "",
@SerialId(3) val tagIconUrl: String = "",
@SerialId(4) val tagHref: String = "",
@SerialId(5) val tagBackColor: String = "",
@SerialId(6) val tagFontColor: String = "",
@SerialId(7) val tagVid: String = "",
@SerialId(8) val tagType: Int = 0,
@SerialId(9) val addTime: Int = 0,
@SerialId(10) val tagCategory: String = "",
@SerialId(11) val tagOtherUrl: String = "",
@SerialId(12) val bid: Int = 0
@ProtoId(1) val tagId: Long = 0L,
@ProtoId(2) val tagName: String = "",
@ProtoId(3) val tagIconUrl: String = "",
@ProtoId(4) val tagHref: String = "",
@ProtoId(5) val tagBackColor: String = "",
@ProtoId(6) val tagFontColor: String = "",
@ProtoId(7) val tagVid: String = "",
@ProtoId(8) val tagType: Int = 0,
@ProtoId(9) val addTime: Int = 0,
@ProtoId(10) val tagCategory: String = "",
@ProtoId(11) val tagOtherUrl: String = "",
@ProtoId(12) val bid: Int = 0
) : ProtoBuf
@Serializable
class ShopID(
@SerialId(1) val shopid: String = "",
@SerialId(2) val sp: Int = 0
@ProtoId(1) val shopid: String = "",
@ProtoId(2) val sp: Int = 0
) : ProtoBuf
@Serializable
class FeedComment(
@SerialId(1) val commentId: String = "",
@SerialId(2) val feedId: String = "",
@SerialId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null,
@SerialId(4) val time: Int = 0,
@SerialId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null,
@SerialId(7) val flag: Int = 0,
@SerialId(8) val msgContent: AppointDefine.RichText? = null,
@SerialId(9) val hot: Int = 0
@ProtoId(1) val commentId: String = "",
@ProtoId(2) val feedId: String = "",
@ProtoId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null,
@ProtoId(4) val time: Int = 0,
@ProtoId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null,
@ProtoId(7) val flag: Int = 0,
@ProtoId(8) val msgContent: AppointDefine.RichText? = null,
@ProtoId(9) val hot: Int = 0
) : ProtoBuf
@Serializable
class ADFeed(
@SerialId(1) val taskId: Int = 0,
@SerialId(2) val style: Int = 0,
@SerialId(3) val content: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val taskId: Int = 0,
@ProtoId(2) val style: Int = 0,
@ProtoId(3) val content: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class Cell(
@SerialId(1) val int32Mcc: Int = -1,
@SerialId(2) val int32Mnc: Int = -1,
@SerialId(3) val int32Lac: Int = -1,
@SerialId(4) val int32Cellid: Int = -1,
@SerialId(5) val int32Rssi: Int = 0
@ProtoId(1) val int32Mcc: Int = -1,
@ProtoId(2) val int32Mnc: Int = -1,
@ProtoId(3) val int32Lac: Int = -1,
@ProtoId(4) val int32Cellid: Int = -1,
@ProtoId(5) val int32Rssi: Int = 0
) : ProtoBuf
@Serializable
class RecentVistorEvent(
@SerialId(1) val eventtype: Int = 0,
@SerialId(2) val eventTinyid: Long = 0L,
@SerialId(3) val unreadCount: Int = 0
@ProtoId(1) val eventtype: Int = 0,
@ProtoId(2) val eventTinyid: Long = 0L,
@ProtoId(3) val unreadCount: Int = 0
) : ProtoBuf
@Serializable
class OrganizerInfo(
@SerialId(1) val hostName: String = "",
@SerialId(2) val hostUrl: String = "",
@SerialId(3) val hostCover: String = ""
@ProtoId(1) val hostName: String = "",
@ProtoId(2) val hostUrl: String = "",
@ProtoId(3) val hostCover: String = ""
) : ProtoBuf
@Serializable
class InterestTag(
@SerialId(1) val tagType: Int = 0,
@SerialId(2) val msgTagList: List<AppointDefine.InterestItem>? = null
@ProtoId(1) val tagType: Int = 0,
@ProtoId(2) val msgTagList: List<AppointDefine.InterestItem>? = null
) : ProtoBuf
@Serializable
class AppointInfoEx(
@SerialId(1) val feedsPicUrl: String = "",
@SerialId(2) val feedsUrl: String = "",
@SerialId(3) val detailTitle: String = "",
@SerialId(4) val detailDescribe: String = "",
@SerialId(5) val showPublisher: Int = 0,
@SerialId(6) val detailPicUrl: String = "",
@SerialId(7) val detailUrl: String = "",
@SerialId(8) val showAttend: Int = 0
@ProtoId(1) val feedsPicUrl: String = "",
@ProtoId(2) val feedsUrl: String = "",
@ProtoId(3) val detailTitle: String = "",
@ProtoId(4) val detailDescribe: String = "",
@ProtoId(5) val showPublisher: Int = 0,
@ProtoId(6) val detailPicUrl: String = "",
@ProtoId(7) val detailUrl: String = "",
@ProtoId(8) val showAttend: Int = 0
) : ProtoBuf
@Serializable
class DateComment(
@SerialId(1) val commentId: String = "",
@SerialId(2) val msgAppointId: AppointDefine.AppointID? = null,
@SerialId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null,
@SerialId(4) val time: Int = 0,
@SerialId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null,
@SerialId(7) val flag: Int = 0,
@SerialId(8) val msgContent: AppointDefine.RichText? = null
@ProtoId(1) val commentId: String = "",
@ProtoId(2) val msgAppointId: AppointDefine.AppointID? = null,
@ProtoId(3) val msgPublisherInfo: AppointDefine.StrangerInfo? = null,
@ProtoId(4) val time: Int = 0,
@ProtoId(6) val msgReplyInfo: AppointDefine.ReplyInfo? = null,
@ProtoId(7) val flag: Int = 0,
@ProtoId(8) val msgContent: AppointDefine.RichText? = null
) : ProtoBuf
@Serializable
class AppointContent(
@SerialId(1) val appointSubject: Int = 0,
@SerialId(2) val payType: Int = 0,
@SerialId(3) val appointDate: Int = 0,
@SerialId(4) val appointGender: Int = 0,
@SerialId(5) val appointIntroduce: String = "",
@SerialId(6) val msgAppointAddress: AppointDefine.AddressInfo? = null,
@SerialId(7) val msgTravelInfo: AppointDefine.TravelInfo? = null
@ProtoId(1) val appointSubject: Int = 0,
@ProtoId(2) val payType: Int = 0,
@ProtoId(3) val appointDate: Int = 0,
@ProtoId(4) val appointGender: Int = 0,
@ProtoId(5) val appointIntroduce: String = "",
@ProtoId(6) val msgAppointAddress: AppointDefine.AddressInfo? = null,
@ProtoId(7) val msgTravelInfo: AppointDefine.TravelInfo? = null
) : ProtoBuf
@Serializable
class FeedInfo(
@SerialId(1) val feedType: Long = 0L,
@SerialId(2) val feedId: String = "",
@SerialId(3) val msgFeedContent: AppointDefine.FeedContent? = null,
@SerialId(4) val msgTopicInfo: AppointDefine.NearbyTopic? = null,
@SerialId(5) val publishTime: Long = 0,
@SerialId(6) val praiseCount: Int = 0,
@SerialId(7) val praiseFlag: Int = 0,
@SerialId(8) val msgPraiseUser: List<AppointDefine.StrangerInfo>? = null,
@SerialId(9) val commentCount: Int = 0,
@SerialId(10) val msgCommentList: List<AppointDefine.FeedComment>? = null,
@SerialId(11) val commentRetAll: Int = 0,
@SerialId(12) val hotFlag: Int = 0,
@SerialId(13) val svrReserved: Long = 0L,
@SerialId(14) val msgHotEntry: AppointDefine.HotEntry? = null
@ProtoId(1) val feedType: Long = 0L,
@ProtoId(2) val feedId: String = "",
@ProtoId(3) val msgFeedContent: AppointDefine.FeedContent? = null,
@ProtoId(4) val msgTopicInfo: AppointDefine.NearbyTopic? = null,
@ProtoId(5) val publishTime: Long = 0,
@ProtoId(6) val praiseCount: Int = 0,
@ProtoId(7) val praiseFlag: Int = 0,
@ProtoId(8) val msgPraiseUser: List<AppointDefine.StrangerInfo>? = null,
@ProtoId(9) val commentCount: Int = 0,
@ProtoId(10) val msgCommentList: List<AppointDefine.FeedComment>? = null,
@ProtoId(11) val commentRetAll: Int = 0,
@ProtoId(12) val hotFlag: Int = 0,
@ProtoId(13) val svrReserved: Long = 0L,
@ProtoId(14) val msgHotEntry: AppointDefine.HotEntry? = null
) : ProtoBuf
@Serializable
class HotTopicList(
@SerialId(1) val topicList: List<AppointDefine.HotTopic>? = null
@ProtoId(1) val topicList: List<AppointDefine.HotTopic>? = null
) : ProtoBuf
@Serializable
class FeedContent(
@SerialId(1) val strPicUrl: List<String> = listOf(),
@SerialId(2) val msgText: AppointDefine.RichText? = null,
@SerialId(3) val hrefUrl: String = "",
@SerialId(5) val groupName: String = "",
@SerialId(6) val groupBulletin: String = "",
@SerialId(7) val feedType: Int = 0,
@SerialId(8) val poiId: String = "",
@SerialId(9) val poiTitle: String = "",
@SerialId(20) val effectiveTime: Int = 0,
@SerialId(21) val expiationTime: Int = 0,
@SerialId(22) val msgLocale: AppointDefine.LocaleInfo? = null,
@SerialId(23) val feedsIndex: Int = 0,
@SerialId(24) val msgAd: AppointDefine.ADFeed? = null,
@SerialId(25) val privateData: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val strPicUrl: List<String> = listOf(),
@ProtoId(2) val msgText: AppointDefine.RichText? = null,
@ProtoId(3) val hrefUrl: String = "",
@ProtoId(5) val groupName: String = "",
@ProtoId(6) val groupBulletin: String = "",
@ProtoId(7) val feedType: Int = 0,
@ProtoId(8) val poiId: String = "",
@ProtoId(9) val poiTitle: String = "",
@ProtoId(20) val effectiveTime: Int = 0,
@ProtoId(21) val expiationTime: Int = 0,
@ProtoId(22) val msgLocale: AppointDefine.LocaleInfo? = null,
@ProtoId(23) val feedsIndex: Int = 0,
@ProtoId(24) val msgAd: AppointDefine.ADFeed? = null,
@ProtoId(25) val privateData: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class TravelInfo(
@SerialId(1) val msgDepartLocale: AppointDefine.LocaleInfo? = null,
@SerialId(2) val msgDestination: AppointDefine.LocaleInfo? = null,
@SerialId(3) val vehicle: Int = 0,
@SerialId(4) val partnerCount: Int = 0,
@SerialId(5) val placePicUrl: String = "",
@SerialId(6) val placeUrl: String = ""
@ProtoId(1) val msgDepartLocale: AppointDefine.LocaleInfo? = null,
@ProtoId(2) val msgDestination: AppointDefine.LocaleInfo? = null,
@ProtoId(3) val vehicle: Int = 0,
@ProtoId(4) val partnerCount: Int = 0,
@ProtoId(5) val placePicUrl: String = "",
@ProtoId(6) val placeUrl: String = ""
) : ProtoBuf
@Serializable
class RecentFreshFeed(
@SerialId(1) val freshFeedInfo: List<AppointDefine.FreshFeedInfo>? = null,
@SerialId(2) val uid: Long = 0L
@ProtoId(1) val freshFeedInfo: List<AppointDefine.FreshFeedInfo>? = null,
@ProtoId(2) val uid: Long = 0L
) : ProtoBuf
@Serializable
class GPS(
@SerialId(1) val int32Lat: Int = 900000000,
@SerialId(2) val int32Lon: Int = 900000000,
@SerialId(3) val int32Alt: Int = -10000000,
@SerialId(4) val int32Type: Int = 0
@ProtoId(1) val int32Lat: Int = 900000000,
@ProtoId(2) val int32Lon: Int = 900000000,
@ProtoId(3) val int32Alt: Int = -10000000,
@ProtoId(4) val int32Type: Int = 0
) : ProtoBuf
@Serializable
class AppointID(
@SerialId(1) val requestId: String = ""
@ProtoId(1) val requestId: String = ""
) : ProtoBuf
@Serializable
class LocaleInfo(
@SerialId(1) val name: String = "",
@SerialId(2) val country: String = "",
@SerialId(3) val province: String = "",
@SerialId(4) val city: String = "",
@SerialId(5) val region: String = "",
@SerialId(6) val poi: String = "",
@SerialId(7) val msgGps: AppointDefine.GPS? = null,
@SerialId(8) val address: String = ""
@ProtoId(1) val name: String = "",
@ProtoId(2) val country: String = "",
@ProtoId(3) val province: String = "",
@ProtoId(4) val city: String = "",
@ProtoId(5) val region: String = "",
@ProtoId(6) val poi: String = "",
@ProtoId(7) val msgGps: AppointDefine.GPS? = null,
@ProtoId(8) val address: String = ""
) : ProtoBuf
@Serializable
class LBSInfo(
@SerialId(1) val msgGps: AppointDefine.GPS? = null,
@SerialId(2) val msgWifis: List<AppointDefine.Wifi>? = null,
@SerialId(3) val msgCells: List<AppointDefine.Cell>? = null
@ProtoId(1) val msgGps: AppointDefine.GPS? = null,
@ProtoId(2) val msgWifis: List<AppointDefine.Wifi>? = null,
@ProtoId(3) val msgCells: List<AppointDefine.Cell>? = null
) : ProtoBuf
@Serializable
class FeedEvent(
@SerialId(1) val eventId: Long = 0L,
@SerialId(2) val time: Int = 0,
@SerialId(3) val eventtype: Int = 0,
@SerialId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null,
@SerialId(5) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@SerialId(6) val eventTips: String = "",
@SerialId(7) val msgComment: AppointDefine.FeedComment? = null,
@SerialId(8) val cancelEventId: Long = 0L
@ProtoId(1) val eventId: Long = 0L,
@ProtoId(2) val time: Int = 0,
@ProtoId(3) val eventtype: Int = 0,
@ProtoId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null,
@ProtoId(5) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@ProtoId(6) val eventTips: String = "",
@ProtoId(7) val msgComment: AppointDefine.FeedComment? = null,
@ProtoId(8) val cancelEventId: Long = 0L
) : ProtoBuf
@Serializable
class FeedsCookie(
@SerialId(1) val strList: List<String> = listOf(),
@SerialId(2) val pose: Int = 0,
@SerialId(3) val cookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val uint64Topics: List<Long>? = null
@ProtoId(1) val strList: List<String> = listOf(),
@ProtoId(2) val pose: Int = 0,
@ProtoId(3) val cookie: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val uint64Topics: List<Long>? = null
) : ProtoBuf
@Serializable
class NearbyTopic(
@SerialId(1) val topicId: Long = 0L,
@SerialId(2) val topic: String = "",
@SerialId(3) val foreword: String = "",
@SerialId(4) val createTime: Int = 0,
@SerialId(5) val updateTime: Int = 0,
@SerialId(6) val hotFlag: Int = 0,
@SerialId(7) val buttonStyle: Int = 0,
@SerialId(8) val buttonSrc: String = "",
@SerialId(9) val backgroundSrc: String = "",
@SerialId(10) val attendeeInfo: String = "",
@SerialId(11) val index: Int = 0,
@SerialId(12) val publishScope: Int = 0,
@SerialId(13) val effectiveTime: Int = 0,
@SerialId(14) val expiationTime: Int = 0,
@SerialId(15) val pushedUsrCount: Int = 0,
@SerialId(16) val timerangeLeft: Int = 0,
@SerialId(17) val timerangeRight: Int = 0,
@SerialId(18) val area: String = ""
@ProtoId(1) val topicId: Long = 0L,
@ProtoId(2) val topic: String = "",
@ProtoId(3) val foreword: String = "",
@ProtoId(4) val createTime: Int = 0,
@ProtoId(5) val updateTime: Int = 0,
@ProtoId(6) val hotFlag: Int = 0,
@ProtoId(7) val buttonStyle: Int = 0,
@ProtoId(8) val buttonSrc: String = "",
@ProtoId(9) val backgroundSrc: String = "",
@ProtoId(10) val attendeeInfo: String = "",
@ProtoId(11) val index: Int = 0,
@ProtoId(12) val publishScope: Int = 0,
@ProtoId(13) val effectiveTime: Int = 0,
@ProtoId(14) val expiationTime: Int = 0,
@ProtoId(15) val pushedUsrCount: Int = 0,
@ProtoId(16) val timerangeLeft: Int = 0,
@ProtoId(17) val timerangeRight: Int = 0,
@ProtoId(18) val area: String = ""
) : ProtoBuf
@Serializable
class NearbyEvent(
@SerialId(1) val eventtype: Int = 0,
@SerialId(2) val msgRankevent: AppointDefine.RankEvent? = null,
@SerialId(3) val eventUin: Long = 0L,
@SerialId(4) val eventTinyid: Long = 0L
@ProtoId(1) val eventtype: Int = 0,
@ProtoId(2) val msgRankevent: AppointDefine.RankEvent? = null,
@ProtoId(3) val eventUin: Long = 0L,
@ProtoId(4) val eventTinyid: Long = 0L
) : ProtoBuf
@Serializable
class Feed(
@SerialId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null,
@SerialId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@SerialId(3) val ownerFlag: Int = 0
@ProtoId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null,
@ProtoId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@ProtoId(3) val ownerFlag: Int = 0
) : ProtoBuf
@Serializable
class ActivityInfo(
@SerialId(2) val name: String = "",
@SerialId(3) val cover: String = "",
@SerialId(4) val url: String = "",
@SerialId(5) val startTime: Int = 0,
@SerialId(6) val endTime: Int = 0,
@SerialId(7) val locName: String = "",
@SerialId(8) val enroll: Long = 0L,
@SerialId(9) val createUin: Long = 0L,
@SerialId(10) val createTime: Int = 0,
@SerialId(11) val organizerInfo: AppointDefine.OrganizerInfo = OrganizerInfo(),
@SerialId(12) val flag: Long? = null
@ProtoId(2) val name: String = "",
@ProtoId(3) val cover: String = "",
@ProtoId(4) val url: String = "",
@ProtoId(5) val startTime: Int = 0,
@ProtoId(6) val endTime: Int = 0,
@ProtoId(7) val locName: String = "",
@ProtoId(8) val enroll: Long = 0L,
@ProtoId(9) val createUin: Long = 0L,
@ProtoId(10) val createTime: Int = 0,
@ProtoId(11) val organizerInfo: AppointDefine.OrganizerInfo = OrganizerInfo(),
@ProtoId(12) val flag: Long? = null
) : ProtoBuf
@Serializable
class HotEntry(
@SerialId(1) val openFlag: Int = 0,
@SerialId(2) val restTime: Int = 0,
@SerialId(3) val foreword: String = "",
@SerialId(4) val backgroundSrc: String = ""
@ProtoId(1) val openFlag: Int = 0,
@ProtoId(2) val restTime: Int = 0,
@ProtoId(3) val foreword: String = "",
@ProtoId(4) val backgroundSrc: String = ""
) : ProtoBuf
@Serializable
class UserFeed(
@SerialId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null,
@SerialId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@SerialId(3) val ownerFlag: Int = 0,
@SerialId(4) val msgActivityInfo: AppointDefine.ActivityInfo? = null
@ProtoId(1) val msgUserInfo: AppointDefine.PublisherInfo? = null,
@ProtoId(2) val msgFeedInfo: AppointDefine.FeedInfo? = null,
@ProtoId(3) val ownerFlag: Int = 0,
@ProtoId(4) val msgActivityInfo: AppointDefine.ActivityInfo? = null
) : ProtoBuf
@Serializable
class Elem(
@SerialId(1) val content: String = "",
@SerialId(2) val msgFaceInfo: AppointDefine.Face? = null
@ProtoId(1) val content: String = "",
@ProtoId(2) val msgFaceInfo: AppointDefine.Face? = null
) : ProtoBuf
@Serializable
class HotFreshFeedList(
@SerialId(1) val msgFeeds: List<AppointDefine.HotUserFeed>? = null,
@SerialId(2) val updateTime: Int = 0
@ProtoId(1) val msgFeeds: List<AppointDefine.HotUserFeed>? = null,
@ProtoId(2) val updateTime: Int = 0
) : ProtoBuf
@Serializable
class RptInterestTag(
@SerialId(1) val interestTags: List<AppointDefine.InterestTag>? = null
@ProtoId(1) val interestTags: List<AppointDefine.InterestTag>? = null
) : ProtoBuf
@Serializable
class AddressInfo(
@SerialId(1) val companyZone: String = "",
@SerialId(2) val companyName: String = "",
@SerialId(3) val companyAddr: String = "",
@SerialId(4) val companyPicUrl: String = "",
@SerialId(5) val companyUrl: String = "",
@SerialId(6) val msgCompanyId: AppointDefine.ShopID? = null
@ProtoId(1) val companyZone: String = "",
@ProtoId(2) val companyName: String = "",
@ProtoId(3) val companyAddr: String = "",
@ProtoId(4) val companyPicUrl: String = "",
@ProtoId(5) val companyUrl: String = "",
@ProtoId(6) val msgCompanyId: AppointDefine.ShopID? = null
) : ProtoBuf
@Serializable
class PublisherInfo(
@SerialId(1) val tinyid: Long = 0L,
@SerialId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val age: Int = 0,
@SerialId(4) val gender: Int = 0,
@SerialId(5) val constellation: String = "",
@SerialId(6) val profession: Int = 0,
@SerialId(7) val distance: String = "",
@SerialId(8) val marriage: Int = 0,
@SerialId(9) val vipinfo: String = "",
@SerialId(10) val recommend: Int = 0,
@SerialId(11) val godflag: Int = 0,
@SerialId(12) val chatflag: Int = 0,
@SerialId(13) val chatupCount: Int = 0,
@SerialId(14) val charm: Int = 0,
@SerialId(15) val charmLevel: Int = 0,
@SerialId(16) val pubNumber: Int = 0,
@SerialId(17) val msgCommonLabel: AppointDefine.CommonLabel? = null,
@SerialId(18) val recentVistorTime: Int = 0,
@SerialId(19) val strangerDeclare: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(20) val friendUin: Long = 0L,
@SerialId(21) val historyFlag: Int = 0,
@SerialId(22) val followflag: Long = 0L
@ProtoId(1) val tinyid: Long = 0L,
@ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val age: Int = 0,
@ProtoId(4) val gender: Int = 0,
@ProtoId(5) val constellation: String = "",
@ProtoId(6) val profession: Int = 0,
@ProtoId(7) val distance: String = "",
@ProtoId(8) val marriage: Int = 0,
@ProtoId(9) val vipinfo: String = "",
@ProtoId(10) val recommend: Int = 0,
@ProtoId(11) val godflag: Int = 0,
@ProtoId(12) val chatflag: Int = 0,
@ProtoId(13) val chatupCount: Int = 0,
@ProtoId(14) val charm: Int = 0,
@ProtoId(15) val charmLevel: Int = 0,
@ProtoId(16) val pubNumber: Int = 0,
@ProtoId(17) val msgCommonLabel: AppointDefine.CommonLabel? = null,
@ProtoId(18) val recentVistorTime: Int = 0,
@ProtoId(19) val strangerDeclare: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(20) val friendUin: Long = 0L,
@ProtoId(21) val historyFlag: Int = 0,
@ProtoId(22) val followflag: Long = 0L
) : ProtoBuf
@Serializable
class HotUserFeed(
@SerialId(1) val feedId: String = "",
@SerialId(2) val praiseCount: Int = 0,
@SerialId(3) val publishUid: Long = 0L,
@SerialId(4) val publishTime: Int = 0
@ProtoId(1) val feedId: String = "",
@ProtoId(2) val praiseCount: Int = 0,
@ProtoId(3) val publishUid: Long = 0L,
@ProtoId(4) val publishTime: Int = 0
) : ProtoBuf
@Serializable
class FreshFeedInfo(
@SerialId(1) val uin: Long = 0L,
@SerialId(2) val time: Int = 0,
@SerialId(3) val feedId: String = "",
@SerialId(4) val feedType: Long = 0L
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val time: Int = 0,
@ProtoId(3) val feedId: String = "",
@ProtoId(4) val feedType: Long = 0L
) : ProtoBuf
@Serializable
class CommonLabel(
@SerialId(1) val lableId: Int = 0,
@SerialId(2) val lableMsgPre: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val lableMsgLast: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val interstName: List<ByteArray>? = null,
@SerialId(5) val interstType: List<Int>? = null
@ProtoId(1) val lableId: Int = 0,
@ProtoId(2) val lableMsgPre: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val lableMsgLast: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val interstName: List<ByteArray>? = null,
@ProtoId(5) val interstType: List<Int>? = null
) : ProtoBuf
@Serializable
class Face(
@SerialId(1) val index: Int = 0
@ProtoId(1) val index: Int = 0
) : ProtoBuf
@Serializable
class StrangerInfo(
@SerialId(1) val tinyid: Long = 0L,
@SerialId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val age: Int = 0,
@SerialId(4) val gender: Int = 0,
@SerialId(5) val dating: Int = 0,
@SerialId(6) val listIdx: Int = 0,
@SerialId(7) val constellation: String = "",
@SerialId(8) val profession: Int = 0,
@SerialId(9) val marriage: Int = 0,
@SerialId(10) val vipinfo: String = "",
@SerialId(11) val recommend: Int = 0,
@SerialId(12) val godflag: Int = 0,
@SerialId(13) val charm: Int = 0,
@SerialId(14) val charmLevel: Int = 0,
@SerialId(15) val uin: Long = 0L
@ProtoId(1) val tinyid: Long = 0L,
@ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val age: Int = 0,
@ProtoId(4) val gender: Int = 0,
@ProtoId(5) val dating: Int = 0,
@ProtoId(6) val listIdx: Int = 0,
@ProtoId(7) val constellation: String = "",
@ProtoId(8) val profession: Int = 0,
@ProtoId(9) val marriage: Int = 0,
@ProtoId(10) val vipinfo: String = "",
@ProtoId(11) val recommend: Int = 0,
@ProtoId(12) val godflag: Int = 0,
@ProtoId(13) val charm: Int = 0,
@ProtoId(14) val charmLevel: Int = 0,
@ProtoId(15) val uin: Long = 0L
) : ProtoBuf
@Serializable
class HotTopic(
@SerialId(1) val id: Long = 0L,
@SerialId(2) val title: String = "",
@SerialId(3) val topicType: Long = 0L,
@SerialId(4) val total: Long = 0L,
@SerialId(5) val times: Long = 0L,
@SerialId(6) val historyTimes: Long = 0L,
@SerialId(7) val bgUrl: String = "",
@SerialId(8) val url: String = "",
@SerialId(9) val extraInfo: String = ""
@ProtoId(1) val id: Long = 0L,
@ProtoId(2) val title: String = "",
@ProtoId(3) val topicType: Long = 0L,
@ProtoId(4) val total: Long = 0L,
@ProtoId(5) val times: Long = 0L,
@ProtoId(6) val historyTimes: Long = 0L,
@ProtoId(7) val bgUrl: String = "",
@ProtoId(8) val url: String = "",
@ProtoId(9) val extraInfo: String = ""
) : ProtoBuf
@Serializable
class DateEvent(
@SerialId(1) val eventId: Long = 0L,
@SerialId(2) val time: Int = 0,
@SerialId(3) val type: Int = 0,
@SerialId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null,
@SerialId(5) val msgDateInfo: AppointDefine.AppointInfo? = null,
@SerialId(6) val attendIdx: Int = 0,
@SerialId(7) val eventTips: String = "",
@SerialId(8) val msgComment: AppointDefine.DateComment? = null,
@SerialId(9) val cancelEventId: Long = 0L
@ProtoId(1) val eventId: Long = 0L,
@ProtoId(2) val time: Int = 0,
@ProtoId(3) val type: Int = 0,
@ProtoId(4) val msgUserInfo: AppointDefine.StrangerInfo? = null,
@ProtoId(5) val msgDateInfo: AppointDefine.AppointInfo? = null,
@ProtoId(6) val attendIdx: Int = 0,
@ProtoId(7) val eventTips: String = "",
@ProtoId(8) val msgComment: AppointDefine.DateComment? = null,
@ProtoId(9) val cancelEventId: Long = 0L
) : ProtoBuf
@Serializable
class AppointInfo(
@SerialId(1) val msgAppointId: AppointDefine.AppointID? = null,
@SerialId(2) val msgAppointment: AppointDefine.AppointContent? = null,
@SerialId(3) val appointStatus: Int = 0,
@SerialId(4) val joinWording: String = "",
@SerialId(5) val viewWording: String = "",
@SerialId(6) val unreadCount: Int = 0,
@SerialId(7) val owner: Int = 0,
@SerialId(8) val join: Int = 0,
@SerialId(9) val view: Int = 0,
@SerialId(10) val commentWording: String = "",
@SerialId(11) val commentNum: Int = 0,
@SerialId(12) val attendStatus: Int = 0,
@SerialId(13) val msgAppointmentEx: AppointDefine.AppointInfoEx? = null
@ProtoId(1) val msgAppointId: AppointDefine.AppointID? = null,
@ProtoId(2) val msgAppointment: AppointDefine.AppointContent? = null,
@ProtoId(3) val appointStatus: Int = 0,
@ProtoId(4) val joinWording: String = "",
@ProtoId(5) val viewWording: String = "",
@ProtoId(6) val unreadCount: Int = 0,
@ProtoId(7) val owner: Int = 0,
@ProtoId(8) val join: Int = 0,
@ProtoId(9) val view: Int = 0,
@ProtoId(10) val commentWording: String = "",
@ProtoId(11) val commentNum: Int = 0,
@ProtoId(12) val attendStatus: Int = 0,
@ProtoId(13) val msgAppointmentEx: AppointDefine.AppointInfoEx? = null
) : ProtoBuf
@Serializable
class UserInfo(
@SerialId(1) val uin: Long = 0L,
@SerialId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val age: Int = 0,
@SerialId(4) val gender: Int = 0,
@SerialId(5) val avatar: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val nickname: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val age: Int = 0,
@ProtoId(4) val gender: Int = 0,
@ProtoId(5) val avatar: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
class ReplyInfo(
@SerialId(1) val commentId: String = "",
@SerialId(2) val msgStrangerInfo: AppointDefine.StrangerInfo? = null
@ProtoId(1) val commentId: String = "",
@ProtoId(2) val msgStrangerInfo: AppointDefine.StrangerInfo? = null
) : ProtoBuf
}

View File

@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
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 {
@Serializable
internal class ExtSnsFrdData(
@SerialId(1) val frdUin: Long = 0L,
@SerialId(91001) val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(101001) val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(141001) val mutualmarkScore: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(151001) val ksingSwitch: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(181001) val lbsShare: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val frdUin: Long = 0L,
@ProtoId(91001) val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(101001) val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(141001) val mutualmarkScore: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(151001) val ksingSwitch: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(181001) val lbsShare: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
internal class RspBody(
@SerialId(1) val msgUpdateData: List<Vec0xd50.ExtSnsFrdData>? = null,
@SerialId(11) val over: Int = 0,
@SerialId(12) val nextStart: Int = 0,
@SerialId(13) val uint64UnfinishedUins: List<Long>? = null
@ProtoId(1) val msgUpdateData: List<Vec0xd50.ExtSnsFrdData>? = null,
@ProtoId(11) val over: Int = 0,
@ProtoId(12) val nextStart: Int = 0,
@ProtoId(13) val uint64UnfinishedUins: List<Long>? = null
) : ProtoBuf
@Serializable
internal class ReqBody(
@SerialId(1) val appid: Long = 0L,
@SerialId(2) val maxPkgSize: Int = 0,
@SerialId(3) val startTime: Int = 0,
@SerialId(4) val startIndex: Int = 0,
@SerialId(5) val reqNum: Int = 0,
@SerialId(6) val uinList: List<Long>? = null,
@SerialId(91001) val reqMusicSwitch: Int = 0,
@SerialId(101001) val reqMutualmarkAlienation: Int = 0,
@SerialId(141001) val reqMutualmarkScore: Int = 0,
@SerialId(151001) val reqKsingSwitch: Int = 0,
@SerialId(181001) val reqMutualmarkLbsshare: Int = 0
@ProtoId(1) val appid: Long = 0L,
@ProtoId(2) val maxPkgSize: Int = 0,
@ProtoId(3) val startTime: Int = 0,
@ProtoId(4) val startIndex: Int = 0,
@ProtoId(5) val reqNum: Int = 0,
@ProtoId(6) val uinList: List<Long>? = null,
@ProtoId(91001) val reqMusicSwitch: Int = 0,
@ProtoId(101001) val reqMutualmarkAlienation: Int = 0,
@ProtoId(141001) val reqMutualmarkScore: Int = 0,
@ProtoId(151001) val reqKsingSwitch: Int = 0,
@ProtoId(181001) val reqMutualmarkLbsshare: Int = 0
) : ProtoBuf
@Serializable
internal class KSingRelationInfo(
@SerialId(1) val flag: Int = 0
@ProtoId(1) val flag: Int = 0
) : ProtoBuf
}
@ -59,21 +59,21 @@ internal class Vec0xd50 : ProtoBuf {
internal class Vec0xd6b : ProtoBuf {
@Serializable
internal class ReqBody(
@SerialId(1) val maxPkgSize: Int = 0,
@SerialId(2) val startTime: Int = 0,
@SerialId(11) val uinList: List<Long>? = null
@ProtoId(1) val maxPkgSize: Int = 0,
@ProtoId(2) val startTime: Int = 0,
@ProtoId(11) val uinList: List<Long>? = null
) : ProtoBuf
@Serializable
internal class RspBody(
@SerialId(11) val msgMutualmarkData: List<Vec0xd6b.MutualMarkData>? = null,
@SerialId(12) val uint64UnfinishedUins: List<Long>? = null
@ProtoId(11) val msgMutualmarkData: List<Vec0xd6b.MutualMarkData>? = null,
@ProtoId(12) val uint64UnfinishedUins: List<Long>? = null
) : ProtoBuf
@Serializable
internal class MutualMarkData(
@SerialId(1) val frdUin: Long = 0L,
@SerialId(2) val result: Int = 0
@ProtoId(1) val frdUin: Long = 0L,
@ProtoId(2) val result: Int = 0
// @SerialId(11) val mutualmarkInfo: List<Mutualmark.MutualMark>? = null
) : ProtoBuf
}
@ -82,26 +82,26 @@ internal class Vec0xd6b : ProtoBuf {
internal class Mutualmark : ProtoBuf {
@Serializable
internal class MutualmarkInfo(
@SerialId(1) val lastActionTime: Long = 0L,
@SerialId(2) val level: Int = 0,
@SerialId(3) val lastChangeTime: Long = 0L,
@SerialId(4) val continueDays: Int = 0,
@SerialId(5) val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val notifyTime: Long = 0L,
@SerialId(7) val iconStatus: Long = 0L,
@SerialId(8) val iconStatusEndTime: Long = 0L,
@SerialId(9) val closeFlag: Int = 0,
@SerialId(10) val resourceInfo: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val lastActionTime: Long = 0L,
@ProtoId(2) val level: Int = 0,
@ProtoId(3) val lastChangeTime: Long = 0L,
@ProtoId(4) val continueDays: Int = 0,
@ProtoId(5) val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val notifyTime: Long = 0L,
@ProtoId(7) val iconStatus: Long = 0L,
@ProtoId(8) val iconStatusEndTime: Long = 0L,
@ProtoId(9) val closeFlag: Int = 0,
@ProtoId(10) val resourceInfo: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
internal class ResourceInfo17(
@SerialId(1) val dynamicUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(2) val staticUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val cartoonUrl: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(4) val cartoonMd5: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val playCartoon: Int = 0,
@SerialId(6) val word: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val dynamicUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(2) val staticUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val cartoonUrl: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(4) val cartoonMd5: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val playCartoon: Int = 0,
@ProtoId(6) val word: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
}

View File

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

View File

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

View File

@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import net.mamoe.mirai.utils.currentTimeSeconds
@ -20,22 +20,22 @@ interface ImgReq : ProtoBuf
@Serializable
internal class GetImgUrlReq(
@SerialId(1) val srcUni: Int,
@SerialId(2) val dstUni: Int,
@SerialId(3) val fileResID: String,//UUID
@ProtoId(1) val srcUni: Int,
@ProtoId(2) val dstUni: Int,
@ProtoId(3) val fileResID: String,//UUID
/**
* UUID例子: 没有找到
*/
@SerialId(4) val urlFlag: Int = 1,
@ProtoId(4) val urlFlag: Int = 1,
//5 unknown, 好像没用
@SerialId(6) val urlType: Int = 4,
@SerialId(7) val requestTerm: Int = 5,//确定
@SerialId(8) val requestPlatformType: Int = 9,//确定
@SerialId(9) val srcFileType: Int = 1,//2=ftn1=picplatform255
@SerialId(10) val innerIP: Int = 0,//确定
@SerialId(11) val addressBook: Int = 0,//[ChatType.internalID]== 1006为1[为CONTACT时] 我觉得发0没问题
@SerialId(12) val buType: Int = 1,//确定
@SerialId(13) val buildVer: String = "8.2.0.1296",//版本号
@SerialId(14) val timestamp: Int = currentTimeSeconds.toInt(),//(pic_up_timestamp)
@SerialId(15) val requestTransferType: Int = 1
@ProtoId(6) val urlType: Int = 4,
@ProtoId(7) val requestTerm: Int = 5,//确定
@ProtoId(8) val requestPlatformType: Int = 9,//确定
@ProtoId(9) val srcFileType: Int = 1,//2=ftn1=picplatform255
@ProtoId(10) val innerIP: Int = 0,//确定
@ProtoId(11) val addressBook: Int = 0,//[ChatType.internalID]== 1006为1[为CONTACT时] 我觉得发0没问题
@ProtoId(12) val buType: Int = 1,//确定
@ProtoId(13) val buildVer: String = "8.2.7.4410",//版本号
@ProtoId(14) val timestamp: Int = currentTimeSeconds.toInt(),//(pic_up_timestamp)
@ProtoId(15) val requestTransferType: Int = 1
) : ImgReq

View File

@ -9,8 +9,8 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
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 {
@Serializable
internal class AppShareInfo(
@SerialId(1) val appshareId: Int = 0,
@SerialId(2) val appshareCookie: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(3) val appshareResource: PluginInfo? = null
@ProtoId(1) val appshareId: Int = 0,
@ProtoId(2) val appshareCookie: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(3) val appshareResource: PluginInfo? = null
) : ProtoBuf
@Serializable
internal class C2CTmpMsgHead(
@SerialId(1) val c2cType: Int = 0,
@SerialId(2) val serviceType: Int = 0,
@SerialId(3) val groupUin: Long = 0L,
@SerialId(4) val groupCode: Long = 0L,
@SerialId(5) val sig: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val sigType: Int = 0,
@SerialId(7) val fromPhone: String = "",
@SerialId(8) val toPhone: String = "",
@SerialId(9) val lockDisplay: Int = 0,
@SerialId(10) val directionFlag: Int = 0,
@SerialId(11) val reserved: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val c2cType: Int = 0,
@ProtoId(2) val serviceType: Int = 0,
@ProtoId(3) val groupUin: Long = 0L,
@ProtoId(4) val groupCode: Long = 0L,
@ProtoId(5) val sig: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val sigType: Int = 0,
@ProtoId(7) val fromPhone: String = "",
@ProtoId(8) val toPhone: String = "",
@ProtoId(9) val lockDisplay: Int = 0,
@ProtoId(10) val directionFlag: Int = 0,
@ProtoId(11) val reserved: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
internal class ContentHead(
@SerialId(1) val pkgNum: Int = 0,
@SerialId(2) val pkgIndex: Int = 0,
@SerialId(3) val divSeq: Int = 0,
@SerialId(4) val autoReply: Int = 0
@ProtoId(1) val pkgNum: Int = 0,
@ProtoId(2) val pkgIndex: Int = 0,
@ProtoId(3) val divSeq: Int = 0,
@ProtoId(4) val autoReply: Int = 0
) : ProtoBuf
@Serializable
internal class DiscussInfo(
@SerialId(1) val discussUin: Long = 0L,
@SerialId(2) val discussType: Int = 0,
@SerialId(3) val discussInfoSeq: Long = 0L,
@SerialId(4) val discussRemark: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(5) val discussName: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val discussUin: Long = 0L,
@ProtoId(2) val discussType: Int = 0,
@ProtoId(3) val discussInfoSeq: Long = 0L,
@ProtoId(4) val discussRemark: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(5) val discussName: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
internal class ExtGroupKeyInfo(
@SerialId(1) val curMaxSeq: Int = 0,
@SerialId(2) val curTime: Long = 0L
@ProtoId(1) val curMaxSeq: Int = 0,
@ProtoId(2) val curTime: Long = 0L
) : ProtoBuf
@Serializable
internal class GroupInfo(
@SerialId(1) val groupCode: Long = 0L,
@SerialId(2) val groupType: Int = 0,
@SerialId(3) val groupInfoSeq: Long = 0L,
@SerialId(4) val groupCard: String = "",
@SerialId(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY,
@SerialId(6) val groupLevel: Int = 0,
@SerialId(7) val groupCardType: Int = 0,
@SerialId(8) val groupName: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val groupCode: Long = 0L,
@ProtoId(2) val groupType: Int = 0,
@ProtoId(3) val groupInfoSeq: Long = 0L,
@ProtoId(4) val groupCard: String = "",
@ProtoId(5) val groupRank: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoId(6) val groupLevel: Int = 0,
@ProtoId(7) val groupCardType: Int = 0,
@ProtoId(8) val groupName: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
internal class Msg(
@SerialId(1) val msgHead: MsgHead,
@SerialId(2) val contentHead: ContentHead? = null,
@SerialId(3) val msgBody: ImMsgBody.MsgBody,
@SerialId(4) val appshareInfo: AppShareInfo? = null
@ProtoId(1) val msgHead: MsgHead,
@ProtoId(2) val contentHead: ContentHead? = null,
@ProtoId(3) val msgBody: ImMsgBody.MsgBody,
@ProtoId(4) val appshareInfo: AppShareInfo? = null
) : ProtoBuf
@Serializable
internal class MsgHead(
@SerialId(1) val fromUin: Long = 0L,
@SerialId(2) val toUin: Long = 0L,
@SerialId(3) val msgType: Int = 0,
@SerialId(4) val c2cCmd: Int = 0,
@SerialId(5) val msgSeq: Int = 0,
@SerialId(6) val msgTime: Int = 0,
@SerialId(7) var msgUid: Long = 0L,
@SerialId(8) val c2cTmpMsgHead: C2CTmpMsgHead? = null,
@SerialId(9) val groupInfo: GroupInfo? = null,
@SerialId(10) val fromAppid: Int = 0,
@SerialId(11) val fromInstid: Int = 0,
@SerialId(12) val userActive: Int = 0,
@SerialId(13) val discussInfo: DiscussInfo? = null,
@SerialId(14) val fromNick: String = "",
@SerialId(15) val authUin: Long = 0L,
@SerialId(16) val authNick: String = "",
@SerialId(17) val msgFlag: Int = 0,
@SerialId(18) val authRemark: String = "",
@SerialId(19) val groupName: String = "",
@SerialId(20) val mutiltransHead: MutilTransHead? = null,
@SerialId(21) val msgInstCtrl: ImMsgHead.InstCtrl? = null,
@SerialId(22) val publicAccountGroupSendFlag: Int = 0,
@SerialId(23) val wseqInC2cMsghead: Int = 0,
@SerialId(24) val cpid: Long = 0L,
@SerialId(25) val extGroupKeyInfo: ExtGroupKeyInfo? = null,
@SerialId(26) val multiCompatibleText: String = "",
@SerialId(27) val authSex: Int = 0,
@SerialId(28) val isSrcMsg: Boolean = false
@ProtoId(1) val fromUin: Long = 0L,
@ProtoId(2) val toUin: Long = 0L,
@ProtoId(3) val msgType: Int = 0,
@ProtoId(4) val c2cCmd: Int = 0,
@ProtoId(5) val msgSeq: Int = 0,
@ProtoId(6) val msgTime: Int = 0,
@ProtoId(7) var msgUid: Long = 0L,
@ProtoId(8) val c2cTmpMsgHead: C2CTmpMsgHead? = null,
@ProtoId(9) val groupInfo: GroupInfo? = null,
@ProtoId(10) val fromAppid: Int = 0,
@ProtoId(11) val fromInstid: Int = 0,
@ProtoId(12) val userActive: Int = 0,
@ProtoId(13) val discussInfo: DiscussInfo? = null,
@ProtoId(14) val fromNick: String = "",
@ProtoId(15) val authUin: Long = 0L,
@ProtoId(16) val authNick: String = "",
@ProtoId(17) val msgFlag: Int = 0,
@ProtoId(18) val authRemark: String = "",
@ProtoId(19) val groupName: String = "",
@ProtoId(20) val mutiltransHead: MutilTransHead? = null,
@ProtoId(21) val msgInstCtrl: ImMsgHead.InstCtrl? = null,
@ProtoId(22) val publicAccountGroupSendFlag: Int = 0,
@ProtoId(23) val wseqInC2cMsghead: Int = 0,
@ProtoId(24) val cpid: Long = 0L,
@ProtoId(25) val extGroupKeyInfo: ExtGroupKeyInfo? = null,
@ProtoId(26) val multiCompatibleText: String = "",
@ProtoId(27) val authSex: Int = 0,
@ProtoId(28) val isSrcMsg: Boolean = false
) : ProtoBuf
@Serializable
internal class MsgType0x210(
@SerialId(1) val subMsgType: Int = 0,
@SerialId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val subMsgType: Int = 0,
@ProtoId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY
) : ProtoBuf
@Serializable
internal class MutilTransHead(
@SerialId(1) val status: Int = 0,
@SerialId(2) val msgId: Int = 0
@ProtoId(1) val status: Int = 0,
@ProtoId(2) val msgId: Int = 0
) : ProtoBuf
@Serializable
internal class PluginInfo(
@SerialId(1) val resId: Int = 0,
@SerialId(2) val pkgName: String = "",
@SerialId(3) val newVer: Int = 0,
@SerialId(4) val resType: Int = 0,
@SerialId(5) val lanType: Int = 0,
@SerialId(6) val priority: Int = 0,
@SerialId(7) val resName: String = "",
@SerialId(8) val resDesc: String = "",
@SerialId(9) val resUrlBig: String = "",
@SerialId(10) val resUrlSmall: String = "",
@SerialId(11) val resConf: String = ""
@ProtoId(1) val resId: Int = 0,
@ProtoId(2) val pkgName: String = "",
@ProtoId(3) val newVer: Int = 0,
@ProtoId(4) val resType: Int = 0,
@ProtoId(5) val lanType: Int = 0,
@ProtoId(6) val priority: Int = 0,
@ProtoId(7) val resName: String = "",
@ProtoId(8) val resDesc: String = "",
@ProtoId(9) val resUrlBig: String = "",
@ProtoId(10) val resUrlSmall: String = "",
@ProtoId(11) val resConf: String = ""
) : ProtoBuf
@Serializable
internal class Uin2Nick(
@SerialId(1) val uin: Long = 0L,
@SerialId(2) val nick: String = ""
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val nick: String = ""
) : ProtoBuf
@Serializable
internal class UinPairMsg(
@SerialId(1) val lastReadTime: Int = 0,
@SerialId(2) val peerUin: Long = 0L,
@SerialId(3) val msgCompleted: Int = 0,
@SerialId(4) val msg: List<Msg>? = null,
@SerialId(5) val unreadMsgNum: Int = 0,
@SerialId(8) val c2cType: Int = 0,
@SerialId(9) val serviceType: Int = 0,
@SerialId(10) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
@ProtoId(1) val lastReadTime: Int = 0,
@ProtoId(2) val peerUin: Long = 0L,
@ProtoId(3) val msgCompleted: Int = 0,
@ProtoId(4) val msg: List<Msg>? = null,
@ProtoId(5) val unreadMsgNum: Int = 0,
@ProtoId(8) val c2cType: Int = 0,
@ProtoId(9) val serviceType: Int = 0,
@ProtoId(10) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY
) : 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
*/
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.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
class Oidb0x769 {
@Serializable
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(3) val str_info: String = "",
// @SerialId(4) val province: String,
@ -27,20 +27,20 @@ class Oidb0x769 {
@Serializable
class QueryUinPackageUsageReq(
@SerialId(1) val type: Int,
@SerialId(2) val uinFileSize: Long = 0
@ProtoId(1) val type: Int,
@ProtoId(2) val uinFileSize: Long = 0
): ProtoBuf
@Serializable
class ConfigSeq(
@SerialId(1) val type: Int, // uint
@SerialId(2) val version: Int // uint
@ProtoId(1) val type: Int, // uint
@ProtoId(2) val version: Int // uint
): ProtoBuf
@Serializable
class DeviceInfo(
@SerialId(1) val brand: String,
@SerialId(2) val model: String
@ProtoId(1) val brand: String,
@ProtoId(2) val model: String
//@SerialId(3) val os: OS,
//@SerialId(4) val cpu: CPU,
//@SerialId(5) val memory: Memory,
@ -51,45 +51,45 @@ class Oidb0x769 {
@Serializable
class OS(
@SerialId(1) val type: Int = 1,
@SerialId(2) val version: String,
@SerialId(3) val sdk: String,
@SerialId(4) val kernel: String,
@SerialId(5) val rom: String
@ProtoId(1) val type: Int = 1,
@ProtoId(2) val version: String,
@ProtoId(3) val sdk: String,
@ProtoId(4) val kernel: String,
@ProtoId(5) val rom: String
): ProtoBuf
@Serializable
class Camera(
@SerialId(1) val primary: Long,
@SerialId(2) val secondary: Long,
@SerialId(3) val flag: Boolean
@ProtoId(1) val primary: Long,
@ProtoId(2) val secondary: Long,
@ProtoId(3) val flag: Boolean
): ProtoBuf
@Serializable
class CPU(
@SerialId(1) val model: String,
@SerialId(2) val frequency: Int,
@SerialId(3) val cores: Int
@ProtoId(1) val model: String,
@ProtoId(2) val frequency: Int,
@ProtoId(3) val cores: Int
): ProtoBuf
@Serializable
class Memory(
@SerialId(1) val total: Int,
@SerialId(2) val process: Int
@ProtoId(1) val total: Int,
@ProtoId(2) val process: Int
): ProtoBuf
@Serializable
class Screen(
@SerialId(1) val model: String,
@SerialId(2) val width: Int,
@SerialId(3) val height: Int,
@SerialId(4) val dpi: Int,
@SerialId(5) val multiTouch: Boolean
@ProtoId(1) val model: String,
@ProtoId(2) val width: Int,
@ProtoId(3) val height: Int,
@ProtoId(4) val dpi: Int,
@ProtoId(5) val multiTouch: Boolean
): ProtoBuf
@Serializable
class Storage(
@SerialId(1) val builtin: Int,
@SerialId(2) val external: Int
@ProtoId(1) val builtin: Int,
@ProtoId(2) val external: Int
): ProtoBuf
}

View File

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

View File

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

View File

@ -9,30 +9,30 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
class StatSvcGetOnline {
@Serializable
class Instance(
@SerialId(1) val instanceId: Int = 0,
@SerialId(2) val clientType: Int = 0
@ProtoId(1) val instanceId: Int = 0,
@ProtoId(2) val clientType: Int = 0
) : ProtoBuf
@Serializable
class ReqBody(
@SerialId(1) val uin: Long = 0L,
@SerialId(2) val appid: Int = 0
@ProtoId(1) val uin: Long = 0L,
@ProtoId(2) val appid: Int = 0
) : ProtoBuf
@Serializable
class RspBody(
@SerialId(1) val errorCode: Int = 0,
@SerialId(2) val errorMsg: String = "",
@SerialId(3) val uin: Long = 0L,
@SerialId(4) val appid: Int = 0,
@SerialId(5) val timeInterval: Int = 0,
@SerialId(6) val msgInstances: List<StatSvcGetOnline.Instance>? = null
@ProtoId(1) val errorCode: Int = 0,
@ProtoId(2) val errorMsg: String = "",
@ProtoId(3) val uin: Long = 0L,
@ProtoId(4) val appid: Int = 0,
@ProtoId(5) val timeInterval: Int = 0,
@ProtoId(6) val msgInstances: List<StatSvcGetOnline.Instance>? = null
) : ProtoBuf
}

View File

@ -9,23 +9,23 @@
package net.mamoe.mirai.qqandroid.network.protocol.data.proto
import kotlinx.serialization.SerialId
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoId
import net.mamoe.mirai.qqandroid.io.ProtoBuf
import kotlin.math.absoluteValue
import kotlin.random.Random
@Serializable
class SyncCookie(
@SerialId(1) val time1: Long? = null, // 1580277992
@SerialId(2) val time: Long, // 1580277992
@SerialId(3) val unknown1: Long = Random.nextLong().absoluteValue,// 678328038
@SerialId(4) val unknown2: Long = Random.nextLong().absoluteValue, // 1687142153
@SerialId(5) val const1: Long = const1_, // 1458467940
@SerialId(11) val const2: Long = const2_, // 2683038258
@SerialId(12) val unknown3: Long = 0x1d,
@SerialId(13) val lastSyncTime: Long? = null,
@SerialId(14) val unknown4: Long = 0
@ProtoId(1) val time1: Long? = null, // 1580277992
@ProtoId(2) val time: Long, // 1580277992
@ProtoId(3) val unknown1: Long = Random.nextLong().absoluteValue,// 678328038
@ProtoId(4) val unknown2: Long = Random.nextLong().absoluteValue, // 1687142153
@ProtoId(5) val const1: Long = const1_, // 1458467940
@ProtoId(11) val const2: Long = const2_, // 2683038258
@ProtoId(12) val unknown3: Long = 0x1d,
@ProtoId(13) val lastSyncTime: Long? = null,
@ProtoId(14) val unknown4: Long = 0
) : ProtoBuf
private val const1_: Long = Random.nextLong().absoluteValue

View File

@ -15,13 +15,11 @@ import kotlinx.io.core.buildPacket
import kotlinx.io.core.writeFully
import net.mamoe.mirai.qqandroid.network.QQAndroidClient
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.writeShortLVByteArray
/**
* Encryption method to be used for packet body.
*/
@UseExperimental(ExperimentalUnsignedTypes::class)
@OptIn(ExperimentalUnsignedTypes::class)
internal interface EncryptMethod {
val id: Int
@ -33,16 +31,6 @@ internal interface EncryptMethodSessionKey : EncryptMethod {
val currentLoginState: Int
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 =
buildPacket {
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
}
inline class EncryptMethodECDH135(override val ecdh: ECDH) :
internal inline class EncryptMethodECDH135(override val ecdh: ECDH) :
EncryptMethodECDH {
override val id: Int get() = 135
}
inline class EncryptMethodECDH7(override val ecdh: ECDH) :
internal inline class EncryptMethodECDH7(override val ecdh: ECDH) :
EncryptMethodECDH {
override val id: Int get() = 7
}
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
/**
* **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 =
buildPacket {
writeByte(1) // const
@ -95,15 +81,15 @@ internal interface EncryptMethodECDH : EncryptMethod {
writeFully(client.randomKey)
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 {
check(it[0].toInt() == 0x04) { "Bad publicKey generated. Expected first element=0x04, got${it[0]}" }
})
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(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 EMPTY_BYTE_ARRAY = 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)
*/
@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)
@OptIn(MiraiInternalAPI::class)
internal inline fun OutgoingPacketFactory<*>.buildOutgoingUniPacket(
client: QQAndroidClient,
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(
client: QQAndroidClient,
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(
commandName: String,
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)
*/
@UseExperimental(MiraiInternalAPI::class)
@OptIn(MiraiInternalAPI::class)
internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket(
client: QQAndroidClient,
bodyType: Byte,
@ -199,7 +167,7 @@ internal inline fun OutgoingPacketFactory<*>.buildLoginOutgoingPacket(
private inline val BRP_STUB get() = ByteReadPacket.Empty
@UseExperimental(MiraiInternalAPI::class)
@OptIn(MiraiInternalAPI::class)
internal inline fun BytePacketBuilder.writeSsoPacket(
client: QQAndroidClient,
subAppId: Long,
@ -265,7 +233,7 @@ internal inline fun BytePacketBuilder.writeSsoPacket(
writeIntLVPacket(lengthOffset = { it + 4 }, builder = body)
}
@UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class)
internal fun BytePacketBuilder.writeOicqRequestPacket(
client: QQAndroidClient,
encryptMethod: EncryptMethod,

View File

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

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