diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..970f61a6f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,2 @@ +[*.{kt, kts}] +max_line_length = 120 diff --git a/.github/ISSUE_TEMPLATE/-------.md b/.github/ISSUE_TEMPLATE/-------.md new file mode 100644 index 000000000..0c4b7ac69 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/-------.md @@ -0,0 +1,10 @@ +--- +name: 疑问 / 帮助 +about: 询问一个问题 +title: '' +labels: question +assignees: '' + +--- + + diff --git a/.github/ISSUE_TEMPLATE/----.md b/.github/ISSUE_TEMPLATE/----.md new file mode 100644 index 000000000..5e47e17b4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/----.md @@ -0,0 +1,21 @@ +--- +name: 特性申请 +about: 申请 mirai 添加新的特性 +title: '' +labels: feature +assignees: '' + +--- + + + diff --git a/.github/ISSUE_TEMPLATE/bug---.md b/.github/ISSUE_TEMPLATE/bug---.md new file mode 100644 index 000000000..1ab7c0442 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug---.md @@ -0,0 +1,22 @@ +--- +name: Bug 报告 +about: 提交一个 bug +title: '' +labels: " bug " +assignees: '' + +--- + +## 问题 + + + + +## 如何复现 + + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e365cd46c..2187fa209 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/.github/workflows/main2.yml b/.github/workflows/main2.yml new file mode 100644 index 000000000..95aba24e3 --- /dev/null +++ b/.github/workflows/main2.yml @@ -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" diff --git a/CHANGELOG.md b/CHANGELOG.md index 738c3690e..c5e69ff72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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.iterator` +- 添加 `LockFreeLinkedList.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 等监听模式. **其他** @@ -144,16 +338,16 @@ TIMPC 这些模块都继承自 `mirai-core`. 现在, 要使用 mirai, 必须依赖于特定的协议模块, 如 `mirai-core-timpc`. 查阅 API 时请查看 `mirai-core`. -每个模块只提供少量的额外方法. 我们会给出详细列表. +每个模块只提供少量的额外方法. 我们会给出详细列表. 在目前的开发中您无需考虑多协议兼容. **Bot 构造** 协议抽象后构造 Bot 需指定协议的 `BotFactory`. -在 JVM 平台, Mirai 通过 classname 自动加载协议模块的 `BotFactory`, 因此若您只使用一套协议, 则无需修改现行源码 +在 JVM 平台, Mirai 通过 classname 自动加载协议模块的 `BotFactory`, 因此若您只使用一套协议, 则无需修改现行源码 **事件** -大部分事件包名修改. +大部分事件包名修改. **UInt -> Long** 修改全部 QQ ID, Group ID 的类型由 UInt 为 Long. @@ -203,21 +397,21 @@ TIMPC - 禁言的扩展函数现在会传递实际函数的返回值 ## `0.7.0` *2019/12/04* -协议 +协议 - 重新分析验证码包, 解决一些无法解析的情况. (这可能会产生新的问题, 遇到后请提交 issue) - 重新分析提交密码包 - *提交验证码仍可能出现问题 (已在 `0.7.5` 修复)* -功能 +功能 - XML 消息 DSL 构造支持 (实验性) (暂不支持发送) - 群成员列表现在包含群主 (原本就应该包含) -- 在消息事件处理中添加获取 `.qq()` 和 `.group()` 的扩展函数. +- 在消息事件处理中添加获取 `.qq()` 和 `.group()` 的扩展函数. - 现在处理群消息时 sender 为 Member (以前为 QQ) - 修改 `Message.concat` 为 `Message.followedBy` - 修改成员权限 `OPERATOR` 为 `ADMINISTRATOR` - **bot.subscribeAll<>() 等函数的 handler lambda 的 receiver 由 Bot 改变为 BotSession**; 此变动不会造成现有代码的修改, 但并不兼容旧版本编译的代码 -性能优化 +性能优化 - 内联 ContactList - 2 个 Contact.sendMessage 重载改为内联扩展函数 **(需要添加 import)** - 其他小优化 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..e851f1164 --- /dev/null +++ b/CONTRIBUTING.md @@ -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) 编写插件, 并发布到社区网站: (建设中) diff --git a/README-eng.md b/README-eng.md index 236fa9166..7f2751305 100644 --- a/README-eng.md +++ b/README-eng.md @@ -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() @@ -23,7 +25,8 @@ repositories{ If your project is a multiplatform project, you should add dependencies for each platform respectively. If your project is not a multiplatform project, you just need to add the platform-specific dependency. -`VERSION` should be replaced with the newest version, say [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) +`VERSION` should be replaced with the newest version, say [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) + Mirai is still under experimental stage, it is suggested to keep the version newest. **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). +[](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). -[](https://www.jetbrains.com/?from=mirai) \ No newline at end of file + 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 . diff --git a/README.md b/README.md index 294c80ca6..cc467d3ea 100644 --- a/README.md +++ b/README.md @@ -1,151 +1,146 @@ -# Mirai +
+ logo
+ + + 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 协议支持的高效率机器人框架 + +这个项目的名字来源于 +

京都动画作品《境界的彼方》栗山未来(Kuriyama Mirai)

+

CRYPTON初音未来为代表的创作与活动(Magical Mirai)

+图标以及形象由画师DazeCake绘制 +
+ +## Mirai + **[English](README-eng.md)** -多平台 **QQ Android 和 TimPC** 协议支持库与高效率的机器人框架. + +多平台 **QQ Android** 和 **TIM PC** 协议支持库与高效率的机器人框架. 纯 Kotlin 实现协议和支持框架,模块全部免费开源。 -目前可运行在 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-core-timpc -TIM PC (2.3.2 版本,2019 年 8 月)协议的实现,相较于 core,仅新增少量 API. 详见 [README.md](mirai-core-timpc/) -支持的功能: -- 消息收发:图片文字复合消息,图片消息 -- 群管功能:群员列表,禁言 -(目前不再更新此协议,请关注上文的安卓协议) +Mirai 目前为快速流转(Moving fast)状态, 增量版本之间可能不具有兼容性,任何功能都可能在没有警告的情况下添加、删除或者更改。 -## Use directly -**直接使用 Mirai(终端环境/网页面板(将来)).** -[Mirai-Console](https://github.com/mamoe/mirai/tree/master/mirai-console) 插件支持, 在终端中启动 Mirai 并获得机器人服务 -本模块还未完善。 +Mirai 源码完全开放, 您可以参考 Mirai 的协议实现来开发其他框架, 但需注明来源并遵守开源协议要求 (AGPLv3)。 -## Use as a library -**mirai-core 为独立设计, 可以作为库内置于任意 Java(JVM)/Android 项目中使用.** +### 开发者 -### Gradle -Mirai 只发布在 `jcenter`, 因此请确保在 `build.gradle` 添加 `jcenter()` 仓库: -```kotlin -repositories{ - jcenter() -} -``` -若您需要使用在跨平台项目, 则要对各个目标平台添加不同的依赖,这与 kotlin 相关跨平台库的依赖是类似的。 -**若您只需要使用在单一平台, 则只需要添加一项该平台的依赖. 如只在 JVM 运行则只需要`-jvm`的依赖** +**了解 mirai 架构**: [Wiki](https://github.com/mamoe/mirai/wiki/Home) -请将 `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 也可能会随时修改.** +#### 使用 mirai 作为服务器,为 mirai 开发插件 -**注意:** -Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。 -只添加 API 模块将无法正常工作。 -现在只推荐使用 TIMPC 协议,请参照下文选择对应目标平台的依赖添加。 +- (官方)`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) 进行接入 -**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 内存 +#### 使用 mirai 为第三方依赖库引入项目 -## Contribution +Demos: [mirai-demos](https://github.com/mamoe/mirai-demos) -我们 (Mamoe Technologies) 将会一直维护这个项目,除非遇到不可抗力因素。 +- `Kotlin` 简略版: [Mirai Guide - Quick Start](/docs/guide_quick_start.md) +- `Kotlin` 新手版: [Mirai Guide - Getting Started](/docs/guide_getting_started.md) +- `Java`: 查看上述 Demos + +### 使用者 + +- [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 { - if (it.kind == BECOME_OPERATOR) - reply("${it.member.id} 成为了管理员") -} -``` - -我们也考虑到了 Java 兼容的问题,这正在计划中,但不是高优先的。 - -1. Clone -2. Import as Gradle project -3. 运行 Demo 程序: [mirai-demo](#mirai-demo) 示例和演示程序 +[](https://github.com/Him188) +[](https://github.com/liujiahua123123) +[](https://github.com/ryoii) +[](https://github.com/jasonczc) +[](https://github.com/PeratX) +[](https://github.com/uebian) +[](https://github.com/Freedom0925) +[](https://github.com/ice1000) +[](https://github.com/PragmaTwice) +[](https://github.com/HoshinoTented) +[](https://github.com/Cyenoch) -## Build Requirements +## 鸣谢 -- Kotlin 1.3.61 -- JDK 8 (required) -- JDK 11(for 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 的授权 +[](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 的授权 -[](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 . diff --git a/build.gradle b/build.gradle deleted file mode 100644 index de1d115e9..000000000 --- a/build.gradle +++ /dev/null @@ -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" } - } -} \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 000000000..583a6598e --- /dev/null +++ b/build.gradle.kts @@ -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") + } +} \ No newline at end of file diff --git a/docs/guide_build_for_mirai.md b/docs/guide_build_for_mirai.md new file mode 100644 index 000000000..a56434a5e --- /dev/null +++ b/docs/guide_build_for_mirai.md @@ -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' + } +} + +``` + diff --git a/docs/guide_getting_started.md b/docs/guide_getting_started.md new file mode 100644 index 000000000..90f6386a6 --- /dev/null +++ b/docs/guide_getting_started.md @@ -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有一定了解,可跳过1,2 + +### 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 + + + jcenter + https://jcenter.bintray.com/ + + +``` +```xml + + + net.mamoe + mirai-core-qqandroid-jvm + 0.23.0 + + +``` diff --git a/docs/guide_quick_start.md b/docs/guide_quick_start.md new file mode 100644 index 000000000..c4e469fdb --- /dev/null +++ b/docs/guide_quick_start.md @@ -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 + + + jcenter + https://jcenter.bintray.com/ + + +``` + +```xml + + + net.mamoe + mirai-core-qqandroid-jvm + 0.23.0 + + +``` + +#### 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 { + if (it.kind == BECOME_OPERATOR) + reply("${it.member.id} 成为了管理员") +} +``` \ No newline at end of file diff --git a/docs/guide_subscribe_events.md b/docs/guide_subscribe_events.md new file mode 100644 index 000000000..df5900bb0 --- /dev/null +++ b/docs/guide_subscribe_events.md @@ -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 { + it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!") +} +``` + +本段语句监听了加入群的事件。 + +### 监听禁言事件 + +在代码中添加 + +```kotlin +miraiBot.subscribeAlways (){ + 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 { + it.group.sendMessage("欢迎 ${it.member.nameCardOrNick} 加入本群!") + } + miraiBot.subscribeAlways (){ + it.group.sendMessage("恭喜老哥 ${it.member.nameCardOrNick} 喜提禁言套餐一份") + } + miraiBot.join() // 等待 Bot 离线, 避免主线程退出 +} +``` + +下面可以参阅[Mirai Guide - Build For Mirai](/docs/guide_build_for_mirai.md),对你的Mirai应用进行打包 + diff --git a/gradle.properties b/gradle.properties index eb4f13c0e..0e6d334e3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file +ktorVersion=1.3.1 \ No newline at end of file diff --git a/gradle/publish-japt.gradle b/gradle/publish-japt.gradle new file mode 100644 index 000000000..083199bfb --- /dev/null +++ b/gradle/publish-japt.gradle @@ -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() + 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 + } + } +} \ No newline at end of file diff --git a/gradle/publish.gradle b/gradle/publish.gradle index 10e5f35f0..842ca6910 100644 --- a/gradle/publish.gradle +++ b/gradle/publish.gradle @@ -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': diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3dbe6db2a..d953e5a4b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/mirai-api-http/README.md b/mirai-api-http/README.md deleted file mode 100644 index d08e19f4e..000000000 --- a/mirai-api-http/README.md +++ /dev/null @@ -1,58 +0,0 @@ -# mirai-api-http - - -Mirai-API-http provides adapter for ALL langugae to access mirai via HTTP protocol.
-
- -**[中文](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
-NOTE that only 1 bot could be control under 1 session, you could have multiple session to control all bots. - -#### Request:
- -| 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:
- -| name | type | example|note| -| --- | --- | --- | --- | -| success |Boolean |true|if this session is authorized| -| session |String |UANSHDKSLAOISN|your session key| - - -#### Response if failed:
- -| name | type | example|note| -| --- | --- | --- | --- | -| success |Boolean |false|if this session is authorized| -| session |String |null|your session key| -| error |int |0|error code| - -#### Error:
- -| code | reason| -| --- | --- | -| 0 | wrong MIRAI API HTTP key | -| 1 | unknown bot number | - - - without session key, you are not able to access any method below.
- session key should be attached to your cookies 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. \ No newline at end of file diff --git a/mirai-api-http/README_CH.md b/mirai-api-http/README_CH.md deleted file mode 100644 index 8fd0da5f1..000000000 --- a/mirai-api-http/README_CH.md +++ /dev/null @@ -1,882 +0,0 @@ -# mirai-api-http - -Mirai-API-http 提供HTTP API供所有语言使用mirai - -## 快速开始 - -```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-Type:multipart/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,图片消息 -+ [ ] Xml,Xml卡片消息 -+ [ ] 敬请期待 - -#### 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": "群头衔" -} -``` \ No newline at end of file diff --git a/mirai-api-http/build.gradle.kts b/mirai-api-http/build.gradle.kts deleted file mode 100644 index dc472dfcf..000000000 --- a/mirai-api-http/build.gradle.kts +++ /dev/null @@ -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")) - } - } -} diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt deleted file mode 100644 index 3e6dcaf75..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/MiraiHttpAPIServer.kt +++ /dev/null @@ -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") - } - } -} \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/Session.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/Session.kt deleted file mode 100644 index fda1a0395..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/Session.kt +++ /dev/null @@ -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 = 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> - - init { - bot.subscribeMessages { - _listener = always { this.run(messageQueue::add) } // this aka messagePacket - } - } - - override fun close() { - _listener.complete() - super.close() - } -} - diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/Exception.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/Exception.kt deleted file mode 100644 index 2f6113472..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/Exception.kt +++ /dev/null @@ -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) \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/StateCode.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/StateCode.kt deleted file mode 100644 index 51766c1cb..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/StateCode.kt +++ /dev/null @@ -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 - } - } -} \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/ContactDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/ContactDTO.kt deleted file mode 100644 index 3381390fb..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/ContactDTO.kt +++ /dev/null @@ -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) -} diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/DTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/DTO.kt deleted file mode 100644 index 157f739bc..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/DTO.kt +++ /dev/null @@ -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 // 反序列化验证成功后传入 -} diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt deleted file mode 100644 index f50f00e11..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/data/common/MessageDTO.kt +++ /dev/null @@ -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 - -@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") -} - - - diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt deleted file mode 100644 index e3a1637ef..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/queue/MessageQueue.kt +++ /dev/null @@ -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>() { - - val quoteCache = ConcurrentHashMap() - - fun fetch(size: Int): List> { - var count = size - quoteCache.clear() - val ret = ArrayList>(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 - } -} \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt deleted file mode 100644 index 40caf465d..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/AuthRouteModule.kt +++ /dev/null @@ -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("/verify", verifiedSessionKey = false) { - val bot = getBotOrThrow(it.qq) - with(SessionManager) { - closeSession(it.sessionKey) - allSession[it.sessionKey] = AuthedSession(bot, EmptyCoroutineContext) - } - call.respondStateCode(StateCode.Success) - } - - miraiVerify("/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 -} \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt deleted file mode 100644 index 6b2c7a3a6..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/BaseRoute.kt +++ /dev/null @@ -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.(AuthDTO) -> Unit -): Route { - return route(path, HttpMethod.Post) { - intercept { - val dto = context.receiveDTO() ?: throw IllegalParamException("参数格式错误") - this.body(dto) - } - } -} - -/** - * Get,用于获取bot的属性 - * 验证请求参数中sessionKey参数的有效性 - */ -@ContextDsl -internal fun Route.miraiGet( - path: String, - body: suspend PipelineContext.(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 Route.miraiVerify( - path: String, - verifiedSessionKey: Boolean = true, - crossinline body: suspend PipelineContext.(T) -> Unit -): Route { - return route(path, HttpMethod.Post) { - intercept { - val dto = context.receiveDTO() ?: 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) = 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 ApplicationCall.respondStateCode(code: T, status: HttpStatusCode = HttpStatusCode.OK) = respondJson(code.toJson(StateCode.serializer()), status) - -internal suspend inline fun 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 ApplicationCall.receiveDTO(): T? = receive().jsonParseOrNull() - - -fun PipelineContext.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 PipelineContext.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.value(name: String) = - try { - (filter { it.name == name }[0] as PartData.FormItem).value - } catch (e: Exception) { - throw IllegalParamException("参数格式错误") - } - -internal fun List.file(name: String) = - try { - filter { it.name == name }[0] as? PartData.FileItem - } catch (e: Exception) { - throw IllegalParamException("参数格式错误") - } \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/EventRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/EventRouteModule.kt deleted file mode 100644 index a65a12102..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/EventRouteModule.kt +++ /dev/null @@ -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 - diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/GroupManageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/GroupManageRouteModule.kt deleted file mode 100644 index 21e4fe021..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/GroupManageRouteModule.kt +++ /dev/null @@ -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("/muteAll") { - it.session.bot.getGroup(it.target).isMuteAll = true - call.respondStateCode(StateCode.Success) - } - - miraiVerify("/unmuteAll") { - it.session.bot.getGroup(it.target).isMuteAll = false - call.respondStateCode(StateCode.Success) - } - - miraiVerify("/mute") { - when (it.session.bot.getGroup(it.target)[it.memberId].mute(it.time)) { - true -> call.respondStateCode(StateCode.Success) - else -> throw PermissionDeniedException - } - } - - miraiVerify("/unmute") { - when (it.session.bot.getGroup(it.target).members[it.memberId].unmute()) { - true -> call.respondStateCode(StateCode.Success) - else -> throw PermissionDeniedException - } - } - - /** - * 移出群聊(需要相关权限) - */ - miraiVerify("/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("/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("/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) -} diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/InfoRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/InfoRouteModule.kt deleted file mode 100644 index e744a1d1f..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/InfoRouteModule.kt +++ /dev/null @@ -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()) - } - } -} \ No newline at end of file diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt deleted file mode 100644 index aa2beb5b8..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/route/SendMessageRouteModule.kt +++ /dev/null @@ -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("/sendFriendMessage") { - it.session.bot.getFriend(it.target).sendMessage(it.messageChain.toMessageChain()) - call.respondStateCode(StateCode.Success) - } - - miraiVerify("/sendGroupMessage") { - it.session.bot.getGroup(it.target).sendMessage(it.messageChain.toMessageChain()) - call.respondStateCode(StateCode.Success) - } - - miraiVerify("/quoteMessage") { - it.session.messageQueue.quoteCache[it.target]?.quoteReply(it.messageChain.toMessageChain()) - ?: throw NoSuchElementException() - call.respondStateCode(StateCode.Success) - } - - miraiVerify("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 -) : VerifyDTO() - diff --git a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt b/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt deleted file mode 100644 index e46fbb1f5..000000000 --- a/mirai-api-http/src/main/kotlin/net/mamoe/mirai/api/http/util/Json.kt +++ /dev/null @@ -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 String.jsonParseOrNull( - serializer: DeserializationStrategy? = 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 T.toJson( - serializer: SerializationStrategy? = null -): String = if (serializer == null) MiraiJson.json.stringify(this) -else MiraiJson.json.stringify(serializer, this) - - -// 序列化列表时,stringify需要使用的泛型是T,而非List -// 因为使用的stringify的stringify(objs: List)重载 -@UseExperimental(ImplicitReflectionSerializer::class, UnstableDefault::class) -inline fun List.toJson( - serializer: SerializationStrategy>? = 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() - } - }) -} \ No newline at end of file diff --git a/mirai-console/README.MD b/mirai-console/README.MD deleted file mode 100644 index 3a6ae7272..000000000 --- a/mirai-console/README.MD +++ /dev/null @@ -1,52 +0,0 @@ -# Mirai Console -#### Mirai Console allows you to run Mirai in command lines/terminal. -#### 你可以终端中或命令行环境下运行在Mirai -
- -#### More Importantly, Mirai Console support Plugins, tells the bot what to do -#### Mirai Console 支持插件系统, 你可以自己开发或使用公开的插件来逻辑化机器人, 如群管 -
- -#### download 下载 -#### how to get/write plugins 如何获取/写插件 -
-
- -### how to use(如何使用) -#### how to run Mirai Console -
    -
  • download mirai-console.jar
  • -
  • open command line/terminal
  • -
  • create a folder and put mirai-console.jar in
  • -
  • cd that folder
  • -
  • "java -jar mirai-console.jar"
  • -
- -
    -
  • 下载mirai-console.jar
  • -
  • 打开终端
  • -
  • 在任何地方创建一个文件夹, 并放入mirai-console.jar
  • -
  • 在终端中打开该文件夹"cd"
  • -
  • 输入"java -jar mirai-console.jar"
  • -
- -#### how to add plugins -
    -
  • After first time of running mirai console
  • -
  • /plugins/folder will be created next to mirai-console.jar
  • -
  • put plugin(.jar) into /plugins/
  • -
  • restart mirai console
  • -
  • checking logger and check if the plugin is loaded successfully
  • -
  • if the plugin has it own Config file, it normally appears in /plugins/{pluginName}/
  • -
- -
    -
  • 在首次运行mirai console后
  • -
  • mirai-console.jar 的同级会出现/plugins/文件夹
  • -
  • 将插件(.jar)放入/plugins/文件夹
  • -
  • 重启mirai console
  • -
  • 在开启后检查日志, 是否成功加载
  • -
  • 如该插件有配置文件, 配置文件一般会创建在/plugins/插件名字/ 文件夹下
  • -
- - diff --git a/mirai-console/build.gradle.kts b/mirai-console/build.gradle.kts deleted file mode 100644 index e822dbf6c..000000000 --- a/mirai-console/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -plugins { - id("kotlinx-serialization") - id("kotlin") - id("java") -} - - -apply(plugin = "com.github.johnrengelman.shadow") - -apply(plugin = "java-library") - -tasks.withType() { - 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 -} \ No newline at end of file diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/Command.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/Command.kt deleted file mode 100644 index bebbd228d..000000000 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/Command.kt +++ /dev/null @@ -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 = mutableMapOf() - - fun getCommands(): Collection { - 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(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 - val description: String - fun onCommand(args: List): Boolean - fun register() -} - -abstract class Command( - override val name: String, - override val alias: List = listOf(), - override val description: String = "" -) : ICommand { - /** - * 最高优先级监听器 - * 如果return [false] 这次指令不会被[PluginBase]的全局onCommand监听器监听 - * */ - open override fun onCommand(args: List): Boolean { - return true - } - - override fun register() { - CommandManager.register(this) - } -} - -class AnonymousCommand internal constructor( - override val name: String, - override val alias: List, - override val description: String, - val onCommand: ICommand.(args: List) -> Boolean -) : ICommand { - override fun onCommand(args: List): Boolean { - return onCommand.invoke(this, args) - } - - override fun register() { - CommandManager.register(this) - } -} - -class CommandBuilder internal constructor() { - var name: String? = null - var alias: List? = null - var description: String = "" - var onCommand: (ICommand.(args: List) -> Boolean)? = null - - fun onCommand(commandProcess: ICommand.(args: List) -> 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() -} - diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsole.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsole.kt deleted file mode 100644 index 47187677d..000000000 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsole.kt +++ /dev/null @@ -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) { - - MiraiConsole.start() - Runtime.getRuntime().addShutdownHook(thread(start = false) { - MiraiConsole.stop() - }) - - } - } -} - - - diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsoleUI.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsoleUI.kt deleted file mode 100644 index 259ed2ea5..000000000 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/MiraiConsoleUI.kt +++ /dev/null @@ -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>().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) { - 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( - val limit: Int = 50 -) : LinkedList(), List { - override fun push(e: T) { - if (size >= limit) { - pollLast() - } - super.push(e) - } -} \ No newline at end of file diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugins/ConfigSection.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/plugins/ConfigSection.kt deleted file mode 100644 index 41991dfb2..000000000 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugins/ConfigSection.kt +++ /dev/null @@ -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 - fun getIntList(key: String): List - fun getFloatList(key: String): List - fun getDoubleList(key: String): List - fun getLongList(key: String): List - 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 - 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 Config.getValue(thisRef: Any?, property: KProperty<*>): T { - return smartCast(property) -} - -inline operator fun Config.setValue(thisRef: Any?, property: KProperty<*>, value: T) { - this[property.name] = value -} - -/* 带有默认值的代理 */ -inline fun Config.withDefault( - noinline defaultValue: () -> T -): ReadWriteProperty { - val default by lazy { defaultValue.invoke() } - return object : ReadWriteProperty { - 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 Config.withDefaultWrite( - noinline defaultValue: () -> T -): WithDefaultWriteLoader { - return WithDefaultWriteLoader(T::class, this, defaultValue, false) -} - -/* 带有默认值且如果为空会写入保存的代理 */ -inline fun Config.withDefaultWriteSave( - noinline defaultValue: () -> T -): WithDefaultWriteLoader { - return WithDefaultWriteLoader(T::class, this, defaultValue, true) -} - -class WithDefaultWriteLoader( - private val _class: KClass, - private val config: Config, - private val defaultValue: () -> T, - private val save: Boolean -) { - operator fun provideDelegate( - thisRef: Any, - prop: KProperty<*> - ): ReadWriteProperty { - val defaultValue by lazy { defaultValue.invoke() } - config.setIfAbsent(prop.name, defaultValue) - if (save) { - config.save() - } - return object : ReadWriteProperty { - 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 Config.smartCast(property: KProperty<*>): T { - return _smartCast(property.name, T::class) -} - -@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST") -fun Config._smartCast(propertyName: String, _class: KClass): 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 { - 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 { - return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString() } - } - - override fun getIntList(key: String): List { - return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toInt() } - } - - override fun getFloatList(key: String): List { - return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toFloat() } - } - - override fun getDoubleList(key: String): List { - return ((get(key) ?: error("ConfigSection does not contain $key ")) as List<*>).map { it.toString().toDouble() } - } - - override fun getLongList(key: String): List { - 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(), 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 { - return this - } - - override fun save() { - - } - - override fun setIfAbsent(key: String, value: Any) { - this.putIfAbsent(key, value)//atomic - } -} - -open class ConfigSectionDelegation( - private val delegation: MutableMap -) : ConfigSection, MutableMap by delegation { - override fun set(key: String, value: Any) { - delegation.put(key, value) - } - - override fun asMap(): Map { - 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> get() = content.entries - override val keys: MutableSet get() = content.keys - override val values: MutableCollection 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) = 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 { - 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( - content, - object : TypeReference() {}, - 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>(content) as LinkedHashMap - ) - ) - } - - 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) - } -} \ No newline at end of file diff --git a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugins/PluginBase.kt b/mirai-console/src/main/kotlin/net/mamoe/mirai/plugins/PluginBase.kt deleted file mode 100644 index e1c8b0a20..000000000 --- a/mirai-console/src/main/kotlin/net/mamoe/mirai/plugins/PluginBase.kt +++ /dev/null @@ -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) { - - } - - - 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,//插件的依赖 - 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(); - - 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 = mutableMapOf() - private val pluginDescriptions: MutableMap = mutableMapOf() - - fun onCommand(command: ICommand, args: List) { - nameToPluginBaseMap.values.forEach { - it.onCommand(command, args) - } - } - - fun getAllPluginDescriptions(): Collection { - return pluginDescriptions.values - } - - /** - * 尝试加载全部插件 - */ - fun loadPlugins() { - val pluginsFound: MutableMap = mutableMapOf() - val pluginsLocation: MutableMap = 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, - existDepends: MutableList - ) { - - 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) - } - } -} - - - diff --git a/mirai-core-qqandroid/README.md b/mirai-core-qqandroid/README.md index 1a0d9b87a..de5653bb7 100644 --- a/mirai-core-qqandroid/README.md +++ b/mirai-core-qqandroid/README.md @@ -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) \ No newline at end of file diff --git a/mirai-core-qqandroid/build.gradle.kts b/mirai-core-qqandroid/build.gradle.kts index 20e38065b..8179cd718 100644 --- a/mirai-core-qqandroid/build.gradle.kts +++ b/mirai-core-qqandroid/build.gradle.kts @@ -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)) } } @@ -119,4 +124,10 @@ kotlin { } } } -} \ No newline at end of file +} +// +//tasks.withType { +// kotlinOptions.jvmTarget = "1.8" +//} + +apply(from = rootProject.file("gradle/publish.gradle")) diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt index 6db7a5835..2249f7d00 100644 --- a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt @@ -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) } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index be688325d..8f85846b3 100644 --- a/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/androidMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -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) \ No newline at end of file +) : 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 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 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 readUTF8LineTo(out: A, limit: Int): Boolean = + this@toKotlinByteReadChannel.readUTF8LineTo(out, limit) + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotFactory.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotFactory.kt index b639d5461..63dfe6935 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotFactory.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/BotFactory.kt @@ -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 } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt index 5cad965a2..d67a04053 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/ContactImpl.kt @@ -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 { 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() is MessageSvc.PbSendMsg.Response.SUCCESS + ) { + source = it + }.sendAndExpect() 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() + @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.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 -) : 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 = 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 { 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() + ) { + 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() } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index c4c7067da..b87883f51 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -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(account, configuration) { +) : BotImpl(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 = ContactList(LockFreeLinkedList()) + + @Deprecated( + "use friends instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("this.friends") + ) + override val qqs: ContactList + get() = friends + override val friends: ContactList = 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 { + @OptIn(LowLevelAPI::class) + override suspend fun _lowLevelQueryGroupList(): Sequence { return network.run { FriendList.GetTroopListSimplify(bot.client) .sendAndExpect(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() + groupCode = groupCode + ).sendAndExpect(retry = 2) } - override suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence = network.run { - var nextUin = 0L - var sequence = sequenceOf() - while (true) { - val data = FriendList.GetTroopMemberList( - client = bot.client, - targetGroupUin = groupUin, - targetGroupCode = groupCode, - nextUin = nextUin - ).sendAndExpect(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 = + network.run { + var nextUin = 0L + var sequence = sequenceOf() + while (true) { + val data = FriendList.GetTroopMemberList( + client = bot.client, + targetGroupUin = groupUin, + targetGroupCode = groupCode, + nextUin = nextUin + ).sendAndExpect(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" } + } } -} \ No newline at end of file + + 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(queryImageUrl(image)).content.toKotlinByteReadChannel() + } +} + +@Suppress("DEPRECATION") +@OptIn(MiraiInternalAPI::class) +internal expect fun io.ktor.utils.io.ByteReadChannel.toKotlinByteReadChannel(): ByteReadChannel \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt new file mode 100644 index 000000000..070d578fb --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/IOFormat.kt @@ -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 dumpTo(serializer: SerializationStrategy, ojb: T, output: Output) + + fun load(deserializer: DeserializationStrategy, input: Input): T +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt similarity index 76% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt index bf381f7ea..9de7aaa05 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/Jce.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/JceOld.kt @@ -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(index)?.id +internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation(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() { - 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 encodeSerializableValue(serializer: SerializationStrategy, value: T) = when (serializer.descriptor) { - is MapLikeDescriptor -> { - val entries = (value as Map<*, *>).entries - val serializer = (serializer as MapLikeSerializer) - 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 encodeSerializableValue(serializer: SerializationStrategy, value: T) = when { + serializer.descriptor.kind == StructureKind.MAP -> { + try { + val entries = (value as Map<*, *>).entries + val serializer = (serializer as MapLikeSerializer) + 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 - 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).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 - return this // 交给 decodeSerializableValue - } + descriptor.kind == StructureKind.LIST -> { + // if (typeParams.isNotEmpty() && typeParams[0] is ByteSerializer) { + // // Array + // 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 decodeNullableSerializableValue(deserializer: DeserializationStrategy): 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).typeParams.isNotEmpty() && (deserializer as ListLikeSerializer).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) - 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(".") ?: "" + (if (this == null) Unit::class else this::class).qualifiedName?.split(".")?.takeLast(2)?.joinToString(".") + ?: "" } fun dumpAsPacket(serializer: SerializationStrategy, obj: T): ByteReadPacket { @@ -742,14 +793,18 @@ class Jce private constructor(private val charset: JceCharset, context: SerialMo return encoder.build() } - override fun dump(serializer: SerializationStrategy, obj: T): ByteArray { - return dumpAsPacket(serializer, obj).readBytes() + override fun dump(serializer: SerializationStrategy, value: T): ByteArray { + return dumpAsPacket(serializer, value).readBytes() } /** * 注意 close [packet]!! */ - fun load(deserializer: DeserializationStrategy, packet: ByteReadPacket, length: Int = packet.remaining.toInt()): T { + fun load( + deserializer: DeserializationStrategy, + 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 Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? { +internal inline fun JceOld.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) -> R): R? { // println("skipping to $tag start") while (true) { if (isEndOfInput) { // 读不了了 @@ -792,33 +847,4 @@ internal inline fun Jce.JceInput.skipToTagOrNull(tag: Int, block: (JceHead) this.skipField(head.type) 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))" - } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt index 31e755546..4f7f60b44 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/ProtoBufWithNullableSupport.kt @@ -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() { - public override val context + internal open inner class ProtobufWriter(private val encoder: ProtobufEncoder) : TaggedEncoder() { + 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 encodeSerializableValue(serializer: SerializationStrategy, 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) 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 dump(serializer: SerializationStrategy, obj: T): ByteArray = plain.dump(serializer, obj) - override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T = plain.load(deserializer, bytes) - override fun install(module: SerialModule) = throw IllegalStateException("You should not install anything to global instance") + override fun dump(serializer: SerializationStrategy, value: T): ByteArray = plain.dump(serializer, value) + override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T = + plain.load(deserializer, bytes) } - override fun dump(serializer: SerializationStrategy, obj: T): ByteArray { + override fun dump(serializer: SerializationStrategy, 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 -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt new file mode 100644 index 000000000..51e453e1b --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceDecoder.kt @@ -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() { + override val updateMode: UpdateMode + get() = UpdateMode.BANNED + + override fun SerialDescriptor.getTag(index: Int): JceTag { + val annotations = this.getElementAnnotations(index) + + val id = annotations.filterIsInstance().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().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 + } +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt new file mode 100644 index 000000000..fe4b7524d --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceInput.kt @@ -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 useHead(crossinline block: (JceHead) -> R): R { + return currentHead.let(block).also { prepareNextHead() } + } + + /** + * 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则返回 `null` + */ + inline fun skipToHeadAndUseIfPossibleOrNull(tag: Int, crossinline block: (JceHead) -> R): R? { + return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() } + } + + /** + * 跳过 [JceHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常 + */ + inline fun skipToHeadAndUseIfPossibleOrFail( + tag: Int, + crossinline message: () -> String = { "tag not found: $tag" }, + crossinline block: (JceHead) -> R + ): R { + return checkNotNull(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() + } +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt new file mode 100644 index 000000000..070f3f61f --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/JceNew.kt @@ -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 dumpTo(serializer: SerializationStrategy, ojb: T, output: Output) { + output.writePacket(JceOld.byCharSet(this.charset).dumpAsPacket(serializer, ojb)) + } + + override fun load(deserializer: DeserializationStrategy, input: Input): T { + return JceDecoder(JceInput(input, charset), context).decodeSerializableValue(deserializer) + } + + override fun dump(serializer: SerializationStrategy, value: T): ByteArray { + return buildPacket { dumpTo(serializer, value, this) }.readBytes() + } + + override fun load(deserializer: DeserializationStrategy, bytes: ByteArray): T { + return load(deserializer, bytes.toReadPacket()) + } + + companion object { + val UTF_8 = Jce(EmptyModule, JceCharset.UTF8) + val GBK = Jce(EmptyModule, JceCharset.GBK) + + fun byCharSet(c: JceCharset): Jce { + return if (c == JceCharset.UTF8) UTF_8 else GBK + } + + internal const val BYTE: Byte = 0 + internal const val DOUBLE: Byte = 5 + internal const val FLOAT: Byte = 4 + internal const val INT: Byte = 2 + internal const val JCE_MAX_STRING_LENGTH = 104857600 + internal const val LIST: Byte = 9 + internal const val LONG: Byte = 3 + internal const val MAP: Byte = 8 + internal const val SHORT: Byte = 1 + internal const val SIMPLE_LIST: Byte = 13 + internal const val STRING1: Byte = 6 + internal const val STRING4: Byte = 7 + internal const val STRUCT_BEGIN: Byte = 10 + internal const val STRUCT_END: Byte = 11 + internal const val ZERO_TYPE: Byte = 12 + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/common.kt new file mode 100644 index 000000000..2065a87ea --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/jce/common.kt @@ -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))" + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt similarity index 82% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt index 5e5e732d4..7b12bee2b 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/SerializationUtils.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/io/serialization/utils.kt @@ -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 ByteArray.loadAs(deserializer: DeserializationStrategy, c: JceCharset = JceCharset.UTF8): T { - return Jce.byCharSet(c).load(deserializer, this) + return Jce.byCharSet(c).load(deserializer, this.toReadPacket()) } -fun BytePacketBuilder.writeJceStruct(serializer: SerializationStrategy, struct: T, charset: JceCharset = JceCharset.GBK) { - this.writePacket(Jce.byCharSet(charset).dumpAsPacket(serializer, struct)) +fun BytePacketBuilder.writeJceStruct( + serializer: SerializationStrategy, + struct: T, + charset: JceCharset = JceCharset.GBK +) { + Jce.byCharSet(charset).dumpTo(serializer, struct, this) } fun ByteReadPacket.readJceStruct( @@ -36,7 +48,8 @@ fun 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 ByteReadPacket.decodeUniPacket(deserializer: DeserializationS fun 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.toByteArray(serializer: SerializationStrategy, c: JceCharset = JceCharset.GBK): ByteArray = Jce.byCharSet(c).dump(serializer, this) +fun T.toByteArray(serializer: SerializationStrategy, c: JceCharset = JceCharset.GBK): ByteArray = + Jce.byCharSet(c).dump(serializer, this) fun BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy, v: T) { this.writeFully(v.toByteArray(serializer)) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt deleted file mode 100644 index c8e77f7d3..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceFromMsg.kt +++ /dev/null @@ -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 = "" -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt new file mode 100644 index 000000000..4b9be436e --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageSourceImpl.kt @@ -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 + + @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( + timeoutMillis = 3000 + ) { + if (it.messageRandom == this@MessageSourceFromSendGroup.messageRandom) { + it.sequenceId + } else null + } + } + + override suspend fun ensureSequenceIdAvailable() { + sequenceIdDeferred.join() + } + + override fun toString(): String { + return "" + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageQQA.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt similarity index 56% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageQQA.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt index ac3c7bade..c2e0eb573 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/MessageQQA.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/message/messages.kt @@ -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= } */ -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 { +@OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) +internal fun MessageChain.toRichTextElems(forGroup: Boolean): MutableList { val elements = mutableListOf() if (this.any()) { 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 { else -> error("unsupported message type: ${it::class.simpleName}") } } + this.forEach(::transformOneMessage) - // if(this.any()){ - elements.add(ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes()))) - // } + if (this.any()) { + // 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()) { + 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.joinToMessageChain(message: MessageChain) { +@OptIn( + MiraiInternalAPI::class, ExperimentalUnsignedTypes::class, MiraiDebugAPI::class, LowLevelAPI::class +) +internal fun List.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}" + } } } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt index 8f061f3d8..be9b4b3b8 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidBotNetworkHandler.kt @@ -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() + private suspend fun registerClientOnline(timeoutMillis: Long = 3000) { + StatSvc.Register(bot.client).sendAndExpect(timeoutMillis) } // caches private val _pendingEnabled = atomic(true) internal val pendingEnabled get() = _pendingEnabled.value - internal var pendingIncomingPackets: LockFreeLinkedList>? = LockFreeLinkedList() + internal var pendingIncomingPackets: LockFreeLinkedList>? = + 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(timeoutMillis = 5000, retry = 2) - + val data = runCatching { + FriendList.GetFriendGroupList( + bot.client, + currentFriendCount, + 150, + 0, + 0 + ).sendAndExpect(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(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 + 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, bot, it.flag2, it.consumer) + KnownPacketFactories.handleIncomingPacket( + it as KnownPacketFactories.IncomingPacket, + 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( + timeoutMillis = bot.configuration.heartbeatTimeoutMillis, + retry = 2 + ) + return null + } Heartbeat.Alive(bot.client) .sendAndExpect( timeoutMillis = bot.configuration.heartbeatTimeoutMillis, @@ -301,10 +363,12 @@ internal class QQAndroidBotNetworkHandler(bot: QQAndroidBot) : BotNetworkHandler */ @Volatile private var cachedPacketTimeoutJob: Job? = null + /** * 缓存的包 */ private val cachedPacket: AtomicRef = 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

generifiedParsePacket(input: Input) { - KnownPacketFactories.parseIncomingPacket(bot, input) { packetFactory: PacketFactory

, packet: P, commandName: String, sequenceId: Int -> - handlePacket(packetFactory, packet, commandName, sequenceId) + private suspend fun

generifiedParsePacket(input: Input) { + KnownPacketFactories.parseIncomingPacket( + bot, + input + ) { packetFactory: PacketFactory

, packet: P, commandName: String, sequenceId: Int -> if (packet is MultiPacket<*>) { packet.forEach { handlePacket(null, it, commandName, sequenceId) } } + handlePacket(packetFactory, packet, commandName, sequenceId) } } /** * 处理解析完成的包. */ - suspend fun

handlePacket(packetFactory: PacketFactory

?, packet: P, commandName: String, sequenceId: Int) { + suspend fun

handlePacket( + packetFactory: PacketFactory

?, + 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

-> bot.handle(packet) + is IncomingPacketFactory

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

-> bot.handle(packet) - is IncomingPacketFactory

-> 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 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 by CompletableDeferred(supervisor) { - fun filter(commandName: String, sequenceId: Int) = this.commandName == commandName && this.sequenceId == sequenceId + ) : CompletableDeferred 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() } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt index 74acb554b..3abcf94ee 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/QQAndroidClient.kt @@ -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 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 internal typealias Pt4TokenMap = MutableMap +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()) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt deleted file mode 100644 index aee309aee..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/Codec.kt +++ /dev/null @@ -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) - } - } - } -} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt index adc5b372e..02989aeb0 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/HighwayHelper.kt @@ -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 { - url { - protocol = URLProtocol.HTTP - host = "htdata2.qq.com" - path("cgi-bin/httpconn") +): Boolean = post { + 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}" } + } } } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt new file mode 100644 index 000000000..64c570031 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/highway/highway.kt @@ -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 { + 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) + } + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt index 9dc876865..9a0545802 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/ConfigPush.kt @@ -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, - @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, + @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, - @SerialId(2) val netSegConfs: List? = null, - @SerialId(3) val ufragmentSize: Long? = null + @JceId(0) val uServiceType: Long, + @JceId(1) val vIplist: List, + @JceId(2) val netSegConfs: List? = 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 + @JceId(0) val vDomainIplists: List ) : 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 + @JceId(0) val uDomainType: Int, + @JceId(1) val vIplist: List ) : JceStruct @Serializable internal class FileStoragePushFSSvcList( - @SerialId(0) val vUpLoadList: List, - @SerialId(1) val vPicDownLoadList: List, - @SerialId(2) val vGPicDownLoadList: List? = null, - @SerialId(3) val vQzoneProxyServiceList: List? = null, - @SerialId(4) val vUrlEncodeServiceList: List? = null, - @SerialId(5) val bigDataChannel: BigDataChannel? = null, - @SerialId(6) val vVipEmotionList: List? = null, - @SerialId(7) val vC2CPicDownList: List? = null, - @SerialId(8) val fmtIPInfo: FmtIPInfo? = null, - @SerialId(9) val domainIpChannel: DomainIpChannel? = null, - @SerialId(10) val pttlist: ByteArray? = null + @JceId(0) val vUpLoadList: List, + @JceId(1) val vPicDownLoadList: List, + @JceId(2) val vGPicDownLoadList: List? = null, + @JceId(3) val vQzoneProxyServiceList: List? = null, + @JceId(4) val vUrlEncodeServiceList: List? = null, + @JceId(5) val bigDataChannel: BigDataChannel? = null, + @JceId(6) val vVipEmotionList: List? = null, + @JceId(7) val vC2CPicDownList: List? = 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, - @SerialId(3) val vWifiList: List, - @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? = null, - @SerialId(9) val vHttpWifilist: List? = null + @JceId(1) val v2G3GList: List, + @JceId(3) val vWifiList: List, + @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? = null, + @JceId(9) val vHttpWifilist: List? = 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 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt index 27e19dd94..bb079399e 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/FriendList.kt @@ -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 + @JceId(0) val dwZero: Long, + @JceId(1) val dwGroupCode: Long, + @JceId(2) val dwNewSeq: Long, + @JceId(3) val vecUinInfo: List ) : 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? = 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? = 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? = 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? = 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? = 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? = 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? = 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? = 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? = 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? = 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 + @JceId(0) val mOpenInfo: Map ) : 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 diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt index 7fc67599d..185390db0 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/OnlinePushPack.kt @@ -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? = 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? = 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? = 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? = 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, - @SerialId(3) val svrip: Int? = 0, - @SerialId(4) val vSyncCookie: ByteArray? = null, - @SerialId(5) val vUinPairMsg: List? = null, - @SerialId(6) val mPreviews: Map? = null + @JceId(0) val uin: Long, + @JceId(1) val uMsgTime: Long, + @JceId(2) val vMsgInfos: List, + @JceId(3) val svrip: Int? = 0, + @JceId(4) val vSyncCookie: ByteArray? = null, + @JceId(5) val vUinPairMsg: List? = null, + @JceId(6) val mPreviews: Map? = 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, - @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, + @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? = null + @JceId(1) val uLastReadTime: Long? = null, + @JceId(2) val peerUin: Long? = null, + @JceId(3) val uMsgCompleted: Long? = null, + @JceId(4) val vMsgInfos: List? = 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? = null + @JceId(0) val vMsgFrdGroup: List? = null ) : JceStruct @Serializable internal class FriendGroup( - @SerialId(0) val dwFuin: Long? = null, - @SerialId(1) val vOldGroupID: List? = null, - @SerialId(2) val vNewGroupID: List? = null + @JceId(0) val dwFuin: Long? = null, + @JceId(1) val vOldGroupID: List? = null, + @JceId(2) val vNewGroupID: List? = 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? = null + @JceId(0) val vMsgGroupSort: List? = 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? = null + @JceId(0) val vPluginNumList: List? = 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 } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt index 5547f798f..f9550fec3 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/PushNotifyPack.kt @@ -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?, - @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?//, + @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?, + @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?//, //@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 \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt index 427ed6800..3f7857b70 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPacket.kt @@ -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() @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? = EMPTY_MAP, - @SerialId(10) val status: Map? = 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? = EMPTY_MAP, + @JceId(10) val status: Map? = EMPTY_MAP ) : JceStruct @Serializable internal class RequestDataVersion3( - @SerialId(0) val map: Map // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的 + @JceId(0) val map: Map // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的 ) : JceStruct @Serializable internal class RequestDataVersion2( - @SerialId(0) val map: Map> + @JceId(0) val map: Map> ) : JceStruct @Serializable internal class RequestDataStructSvcReqRegister( - @SerialId(0) val struct: SvcReqRegister + @JceId(0) val struct: SvcReqRegister ) : JceStruct \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt index 27e71530c..0027473e8 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/RequestPushForceOffline.kt @@ -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 \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt index 1b1c23584..40b568d67 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/SvcReqRegister.kt @@ -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 \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt index e7f1f4fe6..b04ce98a4 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/jce/TroopList.kt @@ -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? = 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? = 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? = null, - @SerialId(6) val vecTroopListDel: List? = null, - @SerialId(7) val vecTroopRank: List? = null, - @SerialId(8) val vecFavGroup: List? = null, - @SerialId(9) val vecTroopListExt: List? = 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? = null, + @JceId(6) val vecTroopListDel: List? = null, + @JceId(7) val vecTroopRank: List? = null, + @JceId(8) val vecFavGroup: List? = null, + @JceId(9) val vecTroopListExt: List? = 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? = 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? = 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, - @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, + @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? = null + @JceId(0) val eStarState: Int? = null, + @JceId(1) val extendInfo: Map? = null ) : JceStruct \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt index 2052c3326..8eca4ba11 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x352.kt @@ -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? = null, - @SerialId(6) val bytesOriginalDownUrl: List? = null, - @SerialId(7) val msgImgInfo: ImgInfo? = null, - @SerialId(8) val uint32DownIp: List? = null, - @SerialId(9) val uint32DownPort: List? = 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? = 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? = 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? = null, + @ProtoId(6) val bytesOriginalDownUrl: List? = null, + @ProtoId(7) val msgImgInfo: ImgInfo? = null, + @ProtoId(8) val uint32DownIp: List? = null, + @ProtoId(9) val uint32DownPort: List? = 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? = 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? = 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? = null,// optional - @SerialId(3) val msgGetimgUrlReq: List? = null,// optional - @SerialId(4) val msgDelImgReq: List? = null, - @SerialId(10) val netType: Int = 3// 数据网络=5 + @ProtoId(1) val subcmd: Int = 0, //2是GetImgUrlReq 1是UploadImgReq + @ProtoId(2) val msgTryupImgReq: List? = null,// optional + @ProtoId(3) val msgGetimgUrlReq: List? = null,// optional + @ProtoId(4) val msgDelImgReq: List? = null, + @ProtoId(10) val netType: Int = 3// 数据网络=5 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val subcmd: Int = 0, - @SerialId(2) val msgTryupImgRsp: List? = null, - @SerialId(3) val msgGetimgUrlRsp: List? = null, - @SerialId(4) val boolNewBigchan: Boolean = false, - @SerialId(5) val msgDelImgRsp: List? = null, - @SerialId(10) val failMsg: String? = "" + @ProtoId(1) val subcmd: Int = 0, + @ProtoId(2) val msgTryupImgRsp: List? = null, + @ProtoId(3) val msgGetimgUrlRsp: List? = null, + @ProtoId(4) val boolNewBigchan: Boolean = false, + @ProtoId(5) val msgDelImgRsp: List? = 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? = null, - @SerialId(8) val uint32UpPort: List? = 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? = 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? = null, + @ProtoId(8) val uint32UpPort: List? = 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? = 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 } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt index d9ab40396..bd818b0e5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x388.kt @@ -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? = null + @ProtoId(1) val bytesExtinfo: List? = null ) : ProtoBuf @Serializable class ExtensionExpRoamTryUp( - @SerialId(1) val msgExproamPicInfo: List? = null + @ProtoId(1) val msgExproamPicInfo: List? = 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? = null, - @SerialId(7) val bytesOriginalDownUrl: List? = null, - @SerialId(8) val bytesBigDownUrl: List? = null, - @SerialId(9) val uint32DownIp: List? = null, - @SerialId(10) val uint32DownPort: List? = 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? = null, - @SerialId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20) val httpsUrlFlag: Int = 0, - @SerialId(26) val msgDownIp6: List? = 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? = null, + @ProtoId(7) val bytesOriginalDownUrl: List? = null, + @ProtoId(8) val bytesBigDownUrl: List? = null, + @ProtoId(9) val uint32DownIp: List? = null, + @ProtoId(10) val uint32DownPort: List? = 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? = null, + @ProtoId(19) val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20) val httpsUrlFlag: Int = 0, + @ProtoId(26) val msgDownIp6: List? = 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? = null, - @SerialId(6) val uint32DownIp: List? = null, - @SerialId(7) val uint32DownPort: List? = 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? = 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? = null, + @ProtoId(6) val uint32DownIp: List? = null, + @ProtoId(7) val uint32DownPort: List? = 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? = 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? = null, - @SerialId(4) val msgGetimgUrlReq: List? = null, - @SerialId(5) val msgTryupPttReq: List? = null, - @SerialId(6) val msgGetpttUrlReq: List? = null, - @SerialId(7) val commandId: Int = 0, - @SerialId(8) val msgDelImgReq: List? = 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? = null, + @ProtoId(4) val msgGetimgUrlReq: List? = null, + @ProtoId(5) val msgTryupPttReq: List? = null, + @ProtoId(6) val msgGetpttUrlReq: List? = null, + @ProtoId(7) val commandId: Int = 0, + @ProtoId(8) val msgDelImgReq: List? = 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? = null, - @SerialId(4) val msgGetimgUrlRsp: List? = null, - @SerialId(5) val msgTryupPttRsp: List? = null, - @SerialId(6) val msgGetpttUrlRsp: List? = null, - @SerialId(7) val msgDelImgRsp: List? = null + @ProtoId(1) val clientIp: Int = 0, + @ProtoId(2) val subcmd: Int = 0, + @ProtoId(3) val msgTryupImgRsp: List? = null, + @ProtoId(4) val msgGetimgUrlRsp: List? = null, + @ProtoId(5) val msgTryupPttRsp: List? = null, + @ProtoId(6) val msgGetpttUrlRsp: List? = null, + @ProtoId(7) val msgDelImgRsp: List? = 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? = null, - @SerialId(7) val uint32UpPort: List? = 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? = 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? = null, + @ProtoId(7) val uint32UpPort: List? = 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? = 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? = null, - @SerialId(6) val uint32UpPort: List? = 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? = 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? = null, + @ProtoId(6) val uint32UpPort: List? = 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? = null, + @ProtoId(27) val clientIp6: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x857.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x857.kt new file mode 100644 index 000000000..ed262fdb9 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x857.kt @@ -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? = 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? = 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? = null, + @ProtoId(2) val msgExcludeInst: List? = 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 = 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? = 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? = 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? = 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? = 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 + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x858.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x858.kt new file mode 100644 index 000000000..001b0cb66 --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Cmd0x858.kt @@ -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? = 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 = 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 +} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Common.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Common.kt deleted file mode 100644 index ec8fa0e6c..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Common.kt +++ /dev/null @@ -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? = 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 -} diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Define.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Define.kt index 22a9d3748..0ec01671d 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Define.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Define.kt @@ -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? = 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? = 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 = 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 = 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? = null + @ProtoId(1) val msgElems: List? = 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? = null + @ProtoId(1) val tagType: Int = 0, + @ProtoId(2) val msgTagList: List? = 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? = null, - @SerialId(9) val commentCount: Int = 0, - @SerialId(10) val msgCommentList: List? = 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? = null, + @ProtoId(9) val commentCount: Int = 0, + @ProtoId(10) val msgCommentList: List? = 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? = null + @ProtoId(1) val topicList: List? = null ) : ProtoBuf @Serializable class FeedContent( - @SerialId(1) val strPicUrl: List = 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 = 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? = null, - @SerialId(2) val uid: Long = 0L + @ProtoId(1) val freshFeedInfo: List? = 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? = null, - @SerialId(3) val msgCells: List? = null + @ProtoId(1) val msgGps: AppointDefine.GPS? = null, + @ProtoId(2) val msgWifis: List? = null, + @ProtoId(3) val msgCells: List? = 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 = listOf(), - @SerialId(2) val pose: Int = 0, - @SerialId(3) val cookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val uint64Topics: List? = null + @ProtoId(1) val strList: List = listOf(), + @ProtoId(2) val pose: Int = 0, + @ProtoId(3) val cookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val uint64Topics: List? = 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? = null, - @SerialId(2) val updateTime: Int = 0 + @ProtoId(1) val msgFeeds: List? = null, + @ProtoId(2) val updateTime: Int = 0 ) : ProtoBuf @Serializable class RptInterestTag( - @SerialId(1) val interestTags: List? = null + @ProtoId(1) val interestTags: List? = 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? = null, - @SerialId(5) val interstType: List? = 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? = null, + @ProtoId(5) val interstType: List? = 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 } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/FriendListCommon.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/FriendListCommon.kt index aa7bf2de0..4680d70f7 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/FriendListCommon.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/FriendListCommon.kt @@ -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? = null, - @SerialId(11) val over: Int = 0, - @SerialId(12) val nextStart: Int = 0, - @SerialId(13) val uint64UnfinishedUins: List? = null + @ProtoId(1) val msgUpdateData: List? = null, + @ProtoId(11) val over: Int = 0, + @ProtoId(12) val nextStart: Int = 0, + @ProtoId(13) val uint64UnfinishedUins: List? = 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? = 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? = 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? = null + @ProtoId(1) val maxPkgSize: Int = 0, + @ProtoId(2) val startTime: Int = 0, + @ProtoId(11) val uinList: List? = null ) : ProtoBuf @Serializable internal class RspBody( - @SerialId(11) val msgMutualmarkData: List? = null, - @SerialId(12) val uint64UnfinishedUins: List? = null + @ProtoId(11) val msgMutualmarkData: List? = null, + @ProtoId(12) val uint64UnfinishedUins: List? = 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? = 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 } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Group.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Group.kt index 73cd71fe5..625147b16 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Group.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Group.kt @@ -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? = null + @ProtoId(1) val error: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val groupInfo: List? = 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? = null + @ProtoId(1) val int32Result: Int = 0, + @ProtoId(2) val groupCode: Long = 0L, + @ProtoId(3) val groupLabel: List? = 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? = 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? = 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 } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt index 59568de2e..8b9db91a0 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Highway.kt @@ -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? = null + @ProtoId(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val int32Retcode: Int = 0, + @ProtoId(3) val msgResult: List? = 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? = null, - @SerialId(8) val msgDropVideoinfo: List? = 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? = null, + @ProtoId(8) val msgDropVideoinfo: List? = 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? = null, - @SerialId(3) val boolCompFlag: Boolean = false + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val dataHole: List? = 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? = null + @ProtoId(1) val ingKey: String = "", + @ProtoId(2) val endPointList: List? = null ) : ProtoBuf @Serializable class HwConfigPB( - @SerialId(1) val configItemList: List? = null, - @SerialId(2) val netSegConfList: List? = null, - @SerialId(3) val shortVideoNetConf: List? = null, - @SerialId(4) val configItemListIp6: List? = null + @ProtoId(1) val configItemList: List? = null, + @ProtoId(2) val netSegConfList: List? = null, + @ProtoId(3) val shortVideoNetConf: List? = null, + @ProtoId(4) val configItemListIp6: List? = 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? = 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? = 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? = 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? = 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? = null, - @SerialId(3) val boolOpenHardwareCodec: Boolean = false + @ProtoId(1) val channelType: Int = 0, + @ProtoId(2) val msgNetsegconf: List? = null, + @ProtoId(3) val boolOpenHardwareCodec: Boolean = false ) : ProtoBuf @Serializable class ShortVideoConf( - @SerialId(1) val channelType: Int = 0, - @SerialId(2) val msgNetsegconf: List? = null, - @SerialId(3) val boolOpenHardwareCodec: Boolean = false, - @SerialId(4) val boolSendAheadSignal: Boolean = false + @ProtoId(1) val channelType: Int = 0, + @ProtoId(2) val msgNetsegconf: List? = 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? = null, - @SerialId(3) val fragmentSize: Int = 0, - @SerialId(4) val msgNetsegconf: List? = null, - @SerialId(5) val msgAddrsV6: List? = null + @ProtoId(1) val serviceType: Int = 0, + @ProtoId(2) val msgAddrs: List? = null, + @ProtoId(3) val fragmentSize: Int = 0, + @ProtoId(4) val msgNetsegconf: List? = null, + @ProtoId(5) val msgAddrsV6: List? = null ) : ProtoBuf } } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/ImageRequest.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/ImageRequest.kt index 09771459d..0b4606896 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/ImageRequest.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/ImageRequest.kt @@ -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=ftn,1=picplatform,255 - @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=ftn,1=picplatform,255 + @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 \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt index f8037384f..a4c0abfdb 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/Msg.kt @@ -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 @@ -21,36 +21,36 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY internal class ImCommon : ProtoBuf { @Serializable internal class GroupInfo( - @SerialId(1) val groupId: Long = 0L, - @SerialId(2) val groupType: Int /* enum */ = 1 + @ProtoId(1) val groupId: Long = 0L, + @ProtoId(2) val groupType: Int /* enum */ = 1 ) : ProtoBuf @Serializable internal class Signature( - @SerialId(1) val keyType: Int = 0, - @SerialId(2) val sessionAppId: Int = 0, - @SerialId(3) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val keyType: Int = 0, + @ProtoId(2) val sessionAppId: Int = 0, + @ProtoId(3) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class Token( - @SerialId(1) val buf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val c2cType: Int = 0, - @SerialId(3) val serviceType: Int = 0 + @ProtoId(1) val buf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val c2cType: Int = 0, + @ProtoId(3) val serviceType: Int = 0 ) : ProtoBuf @Serializable internal class User( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val appId: Int = 0, - @SerialId(3) val instanceId: Int = 0, - @SerialId(4) val appType: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(5) val clientIp: Int = 0, - @SerialId(6) val version: Int = 0, - @SerialId(7) val phoneNumber: String = "", - @SerialId(8) val platformId: Int = 0, - @SerialId(9) val language: Int = 0, - @SerialId(10) val equipKey: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val appId: Int = 0, + @ProtoId(3) val instanceId: Int = 0, + @ProtoId(4) val appType: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(5) val clientIp: Int = 0, + @ProtoId(6) val version: Int = 0, + @ProtoId(7) val phoneNumber: String = "", + @ProtoId(8) val platformId: Int = 0, + @ProtoId(9) val language: Int = 0, + @ProtoId(10) val equipKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @@ -58,37 +58,37 @@ internal class ImCommon : ProtoBuf { internal class ImImagent : ProtoBuf { @Serializable internal class ImAgentHead( - @SerialId(1) val command: Int /* enum */ = 1, - @SerialId(2) val seq: Int = 0, - @SerialId(3) val result: Int = 0, - @SerialId(4) val err: String = "", - @SerialId(5) val echoBuf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val reqUser: ImCommon.User? = null, - @SerialId(7) val reqInfo: Requestinfo? = null, - @SerialId(8) val signature: Signature? = null, - @SerialId(9) val subCmd: Int = 0, - @SerialId(10) val serverIp: Int = 0 + @ProtoId(1) val command: Int /* enum */ = 1, + @ProtoId(2) val seq: Int = 0, + @ProtoId(3) val result: Int = 0, + @ProtoId(4) val err: String = "", + @ProtoId(5) val echoBuf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val reqUser: ImCommon.User? = null, + @ProtoId(7) val reqInfo: Requestinfo? = null, + @ProtoId(8) val signature: Signature? = null, + @ProtoId(9) val subCmd: Int = 0, + @ProtoId(10) val serverIp: Int = 0 ) : ProtoBuf @Serializable internal class ImAgentPackage( - @SerialId(1) val head: ImAgentHead? = null, - @SerialId(11) val msgSendReq: ImMsg.MsgSendReq? = null, - @SerialId(12) val msgSendResp: ImMsg.MsgSendResp? = null + @ProtoId(1) val head: ImAgentHead? = null, + @ProtoId(11) val msgSendReq: ImMsg.MsgSendReq? = null, + @ProtoId(12) val msgSendResp: ImMsg.MsgSendResp? = null ) : ProtoBuf @Serializable internal class Requestinfo( - @ProtoType(ProtoNumberType.FIXED) @SerialId(1) val reqIp: Int = 0, - @SerialId(2) val reqPort: Int = 0, - @SerialId(3) val reqFlag: Int = 0 + @ProtoType(ProtoNumberType.FIXED) @ProtoId(1) val reqIp: Int = 0, + @ProtoId(2) val reqPort: Int = 0, + @ProtoId(3) val reqFlag: Int = 0 ) : ProtoBuf @Serializable internal class Signature( - @SerialId(1) val keyType: Int = 0, - @SerialId(2) val sessionAppId: Int = 0, - @SerialId(3) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val keyType: Int = 0, + @ProtoId(2) val sessionAppId: Int = 0, + @ProtoId(3) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @@ -96,59 +96,59 @@ internal class ImImagent : ProtoBuf { internal class ImMsg : ProtoBuf { @Serializable internal class C2C( - @SerialId(1) val sender: ImCommon.User? = null, - @SerialId(2) val receiver: ImCommon.User? = null, - @SerialId(3) val c2cRelation: C2CRelation? = null + @ProtoId(1) val sender: ImCommon.User? = null, + @ProtoId(2) val receiver: ImCommon.User? = null, + @ProtoId(3) val c2cRelation: C2CRelation? = null ) : ProtoBuf @Serializable internal class C2CRelation( - @SerialId(1) val c2cType: Int /* enum */ = 0, - @SerialId(2) val groupInfo: ImCommon.GroupInfo? = null, - @SerialId(3) val token: ImCommon.Token? = null + @ProtoId(1) val c2cType: Int /* enum */ = 0, + @ProtoId(2) val groupInfo: ImCommon.GroupInfo? = null, + @ProtoId(3) val token: ImCommon.Token? = null ) : ProtoBuf @Serializable internal class ContentHead( - @SerialId(1) val pkgNum: Int = 1, - @SerialId(2) val pkgIndex: Int = 0, - @SerialId(3) val seq: Int = 0, - @SerialId(4) val dateTime: Int = 0, - @SerialId(5) val msgType: Int = 0, - @SerialId(6) val divSeq: Int = 0, - @SerialId(7) val msgdbUin: Long = 0L, - @SerialId(8) val msgdbSeq: Int = 0, - @SerialId(9) val wordMsgSeq: Int = 0, - @SerialId(10) val msgRand: Int = 0 + @ProtoId(1) val pkgNum: Int = 1, + @ProtoId(2) val pkgIndex: Int = 0, + @ProtoId(3) val seq: Int = 0, + @ProtoId(4) val dateTime: Int = 0, + @ProtoId(5) val msgType: Int = 0, + @ProtoId(6) val divSeq: Int = 0, + @ProtoId(7) val msgdbUin: Long = 0L, + @ProtoId(8) val msgdbSeq: Int = 0, + @ProtoId(9) val wordMsgSeq: Int = 0, + @ProtoId(10) val msgRand: Int = 0 ) : ProtoBuf @Serializable internal class Group( - @SerialId(1) val sender: ImCommon.User? = null, - @SerialId(2) val receiver: ImCommon.User? = null, - @SerialId(3) val groupInfo: ImCommon.GroupInfo? = null + @ProtoId(1) val sender: ImCommon.User? = null, + @ProtoId(2) val receiver: ImCommon.User? = null, + @ProtoId(3) val groupInfo: ImCommon.GroupInfo? = null ) : ProtoBuf @Serializable internal class Msg( - @SerialId(1) val head: MsgHead? = null, - @SerialId(2) val body: ImMsgBody.MsgBody? = null + @ProtoId(1) val head: MsgHead? = null, + @ProtoId(2) val body: ImMsgBody.MsgBody? = null ) : ProtoBuf @Serializable internal class MsgHead( - @SerialId(1) val routingHead: RoutingHead? = null, - @SerialId(2) val contentHead: ContentHead? = null, - @SerialId(3) val gbkTmpMsgBody: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val routingHead: RoutingHead? = null, + @ProtoId(2) val contentHead: ContentHead? = null, + @ProtoId(3) val gbkTmpMsgBody: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgSendReq( - @SerialId(1) val msg: Msg? = null, - @SerialId(2) val buMsg: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val msgTailId: Int = 0, - @SerialId(4) val connMsgFlag: Int = 0, - @SerialId(5) val cookie: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val msg: Msg? = null, + @ProtoId(2) val buMsg: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgTailId: Int = 0, + @ProtoId(4) val connMsgFlag: Int = 0, + @ProtoId(5) val cookie: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable @@ -156,8 +156,8 @@ internal class ImMsg : ProtoBuf { @Serializable internal class RoutingHead( - @SerialId(1) val c2c: C2C? = null, - @SerialId(2) val group: Group? = null + @ProtoId(1) val c2c: C2C? = null, + @ProtoId(2) val group: Group? = null ) : ProtoBuf } @@ -165,512 +165,512 @@ internal class ImMsg : ProtoBuf { internal class ImMsgBody : ProtoBuf { @Serializable internal class AnonymousGroupMsg( - @SerialId(1) val flags: Int = 0, - @SerialId(2) val anonId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val anonNick: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val headPortrait: Int = 0, - @SerialId(5) val expireTime: Int = 0, - @SerialId(6) val bubbleId: Int = 0, - @SerialId(7) val rankColor: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val flags: Int = 0, + @ProtoId(2) val anonId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val anonNick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val headPortrait: Int = 0, + @ProtoId(5) val expireTime: Int = 0, + @ProtoId(6) val bubbleId: Int = 0, + @ProtoId(7) val rankColor: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ApolloActMsg( - @SerialId(1) val actionId: Int = 0, - @SerialId(2) val actionName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val actionText: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val flag: Int = 0, - @SerialId(5) val peerUin: Int = 0, - @SerialId(6) val senderTs: Int = 0, - @SerialId(7) val peerTs: Int = 0, - @SerialId(8) val int32SenderStatus: Int = 0, - @SerialId(9) val int32PeerStatus: Int = 0, - @SerialId(10) val diytextId: Int = 0, - @SerialId(11) val diytextContent: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val inputText: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(13) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val actionId: Int = 0, + @ProtoId(2) val actionName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val actionText: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val flag: Int = 0, + @ProtoId(5) val peerUin: Int = 0, + @ProtoId(6) val senderTs: Int = 0, + @ProtoId(7) val peerTs: Int = 0, + @ProtoId(8) val int32SenderStatus: Int = 0, + @ProtoId(9) val int32PeerStatus: Int = 0, + @ProtoId(10) val diytextId: Int = 0, + @ProtoId(11) val diytextContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val inputText: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(13) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ArkAppElem( - @SerialId(1) val appName: String = "", - @SerialId(2) val minVersion: String = "", - @SerialId(3) val xmlTemplate: String = "", - @SerialId(4) val data: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val appName: String = "", + @ProtoId(2) val minVersion: String = "", + @ProtoId(3) val xmlTemplate: String = "", + @ProtoId(4) val data: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class Attr( - @ProtoType(ProtoNumberType.SIGNED) @SerialId(1) val codePage: Int = -1, - @SerialId(2) val time: Int = 1, - @SerialId(3) val random: Int = 0, - @SerialId(4) val color: Int = 0, - @SerialId(5) val size: Int = 10, - @SerialId(6) val effect: Int = 7, - @SerialId(7) val charSet: Int = 78, - @SerialId(8) val pitchAndFamily: Int = 90, - @SerialId(9) val fontName: String = "Times New Roman", - @SerialId(10) val reserveData: ByteArray = EMPTY_BYTE_ARRAY + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(1) val codePage: Int = -1, + @ProtoId(2) val time: Int = 1, + @ProtoId(3) val random: Int = 0, + @ProtoId(4) val color: Int = 0, + @ProtoId(5) val size: Int = 10, + @ProtoId(6) val effect: Int = 7, + @ProtoId(7) val charSet: Int = 78, + @ProtoId(8) val pitchAndFamily: Int = 90, + @ProtoId(9) val fontName: String = "Times New Roman", + @ProtoId(10) val reserveData: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class BitAppMsg( - @SerialId(1) val buf: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val buf: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class BlessingMessage( - @SerialId(1) val msgType: Int = 0, - @SerialId(2) val exFlag: Int = 0 + @ProtoId(1) val msgType: Int = 0, + @ProtoId(2) val exFlag: Int = 0 ) : ProtoBuf @Serializable internal class CommonElem( - @SerialId(1) val serviceType: Int = 0, - @SerialId(2) val pbElem: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val businessType: Int = 0 + @ProtoId(1) val serviceType: Int = 0, + @ProtoId(2) val pbElem: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val businessType: Int = 0 ) : ProtoBuf @Serializable internal class ConferenceTipsInfo( - @SerialId(1) val sessionType: Int = 0, - @SerialId(2) val sessionUin: Long = 0L, - @SerialId(3) val text: String = "" + @ProtoId(1) val sessionType: Int = 0, + @ProtoId(2) val sessionUin: Long = 0L, + @ProtoId(3) val text: String = "" ) : ProtoBuf @Serializable internal class CrmElem( - @SerialId(1) val crmBuf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val qidianFlag: Int = 0, - @SerialId(4) val pushFlag: Int = 0, - @SerialId(5) val countFlag: Int = 0 + @ProtoId(1) val crmBuf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val qidianFlag: Int = 0, + @ProtoId(4) val pushFlag: Int = 0, + @ProtoId(5) val countFlag: Int = 0 ) : ProtoBuf @Serializable internal class CustomElem( - @SerialId(1) val desc: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val data: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val enumType: Int /* enum */ = 1, - @SerialId(4) val ext: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val sound: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val desc: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val data: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val enumType: Int /* enum */ = 1, + @ProtoId(4) val ext: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val sound: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class CustomFace( - @SerialId(1) val guid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val filePath: String = "", - @SerialId(3) val shortcut: String = "", - @SerialId(4) val buffer: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val flag: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val oldData: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val fileId: Int = 0, - @SerialId(8) val serverIp: Int = 0, - @SerialId(9) val serverPort: Int = 0, - @SerialId(10) val fileType: Int = 0, - @SerialId(11) val signature: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val useful: Int = 0, - @SerialId(13) val md5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(14) val thumbUrl: String = "", - @SerialId(15) val bigUrl: String = "", - @SerialId(16) val origUrl: String = "", - @SerialId(17) val bizType: Int = 0, - @SerialId(18) val repeatIndex: Int = 0, - @SerialId(19) val repeatImage: Int = 0, - @SerialId(20) val imageType: Int = 0, - @SerialId(21) val index: Int = 0, - @SerialId(22) val width: Int = 0, - @SerialId(23) val height: Int = 0, - @SerialId(24) val source: Int = 0, - @SerialId(25) val size: Int = 0, - @SerialId(26) val origin: Int = 0, - @SerialId(27) val thumbWidth: Int = 0, - @SerialId(28) val thumbHeight: Int = 0, - @SerialId(29) val showLen: Int = 0, - @SerialId(30) val downloadLen: Int = 0, - @SerialId(31) val _400Url: String = "", - @SerialId(32) val _400Width: Int = 0, - @SerialId(33) val _400Height: Int = 0, - @SerialId(34) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val guid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val filePath: String = "", + @ProtoId(3) val shortcut: String = "", + @ProtoId(4) val buffer: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val flag: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val oldData: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val fileId: Int = 0, + @ProtoId(8) val serverIp: Int = 0, + @ProtoId(9) val serverPort: Int = 0, + @ProtoId(10) val fileType: Int = 0, + @ProtoId(11) val signature: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val useful: Int = 0, + @ProtoId(13) val md5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(14) val thumbUrl: String = "", + @ProtoId(15) val bigUrl: String = "", + @ProtoId(16) val origUrl: String = "", + @ProtoId(17) val bizType: Int = 0, + @ProtoId(18) val repeatIndex: Int = 0, + @ProtoId(19) val repeatImage: Int = 0, + @ProtoId(20) val imageType: Int = 0, + @ProtoId(21) val index: Int = 0, + @ProtoId(22) val width: Int = 0, + @ProtoId(23) val height: Int = 0, + @ProtoId(24) val source: Int = 0, + @ProtoId(25) val size: Int = 0, + @ProtoId(26) val origin: Int = 0, + @ProtoId(27) val thumbWidth: Int = 0, + @ProtoId(28) val thumbHeight: Int = 0, + @ProtoId(29) val showLen: Int = 0, + @ProtoId(30) val downloadLen: Int = 0, + @ProtoId(31) val _400Url: String = "", + @ProtoId(32) val _400Width: Int = 0, + @ProtoId(33) val _400Height: Int = 0, + @ProtoId(34) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class DeliverGiftMsg( - @SerialId(1) val grayTipContent: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val animationPackageId: Int = 0, - @SerialId(3) val animationPackageUrlA: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val animationPackageUrlI: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val remindBrief: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val giftId: Int = 0, - @SerialId(7) val giftCount: Int = 0, - @SerialId(8) val animationBrief: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val senderUin: Long = 0L, - @SerialId(10) val receiverUin: Long = 0L, - @SerialId(11) val stmessageTitle: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val stmessageSubtitle: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(13) val stmessageMessage: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(14) val stmessageGiftpicid: Int = 0, - @SerialId(15) val stmessageComefrom: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(16) val stmessageExflag: Int = 0, - @SerialId(17) val toAllGiftId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(18) val comefromLink: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20) val receiverName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(21) val receiverPic: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(22) val stmessageGifturl: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val grayTipContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val animationPackageId: Int = 0, + @ProtoId(3) val animationPackageUrlA: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val animationPackageUrlI: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val remindBrief: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val giftId: Int = 0, + @ProtoId(7) val giftCount: Int = 0, + @ProtoId(8) val animationBrief: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val senderUin: Long = 0L, + @ProtoId(10) val receiverUin: Long = 0L, + @ProtoId(11) val stmessageTitle: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val stmessageSubtitle: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(13) val stmessageMessage: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(14) val stmessageGiftpicid: Int = 0, + @ProtoId(15) val stmessageComefrom: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(16) val stmessageExflag: Int = 0, + @ProtoId(17) val toAllGiftId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(18) val comefromLink: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20) val receiverName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(21) val receiverPic: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(22) val stmessageGifturl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class EIMInfo( - @SerialId(1) val rootId: Long = 0L, - @SerialId(2) val flag: Int = 0 + @ProtoId(1) val rootId: Long = 0L, + @ProtoId(2) val flag: Int = 0 ) : ProtoBuf @Serializable internal class Elem( - @SerialId(1) val text: Text? = null, - @SerialId(2) val face: Face? = null, - @SerialId(3) val onlineImage: OnlineImage? = null, - @SerialId(4) val notOnlineImage: NotOnlineImage? = null, - @SerialId(5) val transElemInfo: TransElem? = null, - @SerialId(6) val marketFace: MarketFace? = null, - @SerialId(7) val elemFlags: ElemFlags? = null, - @SerialId(8) val customFace: CustomFace? = null, - @SerialId(9) val elemFlags2: ElemFlags2? = null, - @SerialId(10) val funFace: FunFace? = null, - @SerialId(11) val secretFile: SecretFileMsg? = null, - @SerialId(12) val richMsg: RichMsg? = null, - @SerialId(13) val groupFile: GroupFile? = null, - @SerialId(14) val pubGroup: PubGroup? = null, - @SerialId(15) val marketTrans: MarketTrans? = null, - @SerialId(16) val extraInfo: ExtraInfo? = null, - @SerialId(17) val shakeWindow: ShakeWindow? = null, - @SerialId(18) val pubAccount: PubAccount? = null, - @SerialId(19) val videoFile: VideoFile? = null, - @SerialId(20) val tipsInfo: TipsInfo? = null, - @SerialId(21) val anonGroupMsg: AnonymousGroupMsg? = null, - @SerialId(22) val qqLiveOld: QQLiveOld? = null, - @SerialId(23) val lifeOnline: LifeOnlineAccount? = null, - @SerialId(24) val qqwalletMsg: QQWalletMsg? = null, - @SerialId(25) val crmElem: CrmElem? = null, - @SerialId(26) val conferenceTipsInfo: ConferenceTipsInfo? = null, - @SerialId(27) val redbagInfo: RedBagInfo? = null, - @SerialId(28) val lowVersionTips: LowVersionTips? = null, - @SerialId(29) val bankcodeCtrlInfo: ByteArray? = null, - @SerialId(30) val nearByMsg: NearByMessageType? = null, - @SerialId(31) val customElem: CustomElem? = null, - @SerialId(32) val locationInfo: LocationInfo? = null, - @SerialId(33) val pubAccInfo: PubAccInfo? = null, - @SerialId(34) val smallEmoji: SmallEmoji? = null, - @SerialId(35) val fsjMsgElem: FSJMessageElem? = null, - @SerialId(36) val arkApp: ArkAppElem? = null, - @SerialId(37) val generalFlags: GeneralFlags? = null, - @SerialId(38) val hcFlashPic: CustomFace? = null, - @SerialId(39) val deliverGiftMsg: DeliverGiftMsg? = null, - @SerialId(40) val bitappMsg: BitAppMsg? = null, - @SerialId(41) val openQqData: OpenQQData? = null, - @SerialId(42) val apolloMsg: ApolloActMsg? = null, - @SerialId(43) val groupPubAccInfo: GroupPubAccountInfo? = null, - @SerialId(44) val blessMsg: BlessingMessage? = null, - @SerialId(45) val srcMsg: SourceMsg? = null, - @SerialId(46) val lolaMsg: LolaMsg? = null, - @SerialId(47) val groupBusinessMsg: GroupBusinessMsg? = null, - @SerialId(48) val msgWorkflowNotify: WorkflowNotifyMsg? = null, - @SerialId(49) val patElem: PatsElem? = null, - @SerialId(50) val groupPostElem: GroupPostElem? = null, - @SerialId(51) val lightApp: LightAppElem? = null, - @SerialId(52) val eimInfo: EIMInfo? = null, - @SerialId(53) val commonElem: CommonElem? = null + @ProtoId(1) val text: Text? = null, + @ProtoId(2) val face: Face? = null, + @ProtoId(3) val onlineImage: OnlineImage? = null, + @ProtoId(4) val notOnlineImage: NotOnlineImage? = null, + @ProtoId(5) val transElemInfo: TransElem? = null, + @ProtoId(6) val marketFace: MarketFace? = null, + @ProtoId(7) val elemFlags: ElemFlags? = null, + @ProtoId(8) val customFace: CustomFace? = null, + @ProtoId(9) val elemFlags2: ElemFlags2? = null, + @ProtoId(10) val funFace: FunFace? = null, + @ProtoId(11) val secretFile: SecretFileMsg? = null, + @ProtoId(12) val richMsg: RichMsg? = null, + @ProtoId(13) val groupFile: GroupFile? = null, + @ProtoId(14) val pubGroup: PubGroup? = null, + @ProtoId(15) val marketTrans: MarketTrans? = null, + @ProtoId(16) val extraInfo: ExtraInfo? = null, + @ProtoId(17) val shakeWindow: ShakeWindow? = null, + @ProtoId(18) val pubAccount: PubAccount? = null, + @ProtoId(19) val videoFile: VideoFile? = null, + @ProtoId(20) val tipsInfo: TipsInfo? = null, + @ProtoId(21) val anonGroupMsg: AnonymousGroupMsg? = null, + @ProtoId(22) val qqLiveOld: QQLiveOld? = null, + @ProtoId(23) val lifeOnline: LifeOnlineAccount? = null, + @ProtoId(24) val qqwalletMsg: QQWalletMsg? = null, + @ProtoId(25) val crmElem: CrmElem? = null, + @ProtoId(26) val conferenceTipsInfo: ConferenceTipsInfo? = null, + @ProtoId(27) val redbagInfo: RedBagInfo? = null, + @ProtoId(28) val lowVersionTips: LowVersionTips? = null, + @ProtoId(29) val bankcodeCtrlInfo: ByteArray? = null, + @ProtoId(30) val nearByMsg: NearByMessageType? = null, + @ProtoId(31) val customElem: CustomElem? = null, + @ProtoId(32) val locationInfo: LocationInfo? = null, + @ProtoId(33) val pubAccInfo: PubAccInfo? = null, + @ProtoId(34) val smallEmoji: SmallEmoji? = null, + @ProtoId(35) val fsjMsgElem: FSJMessageElem? = null, + @ProtoId(36) val arkApp: ArkAppElem? = null, + @ProtoId(37) val generalFlags: GeneralFlags? = null, + @ProtoId(38) val hcFlashPic: CustomFace? = null, + @ProtoId(39) val deliverGiftMsg: DeliverGiftMsg? = null, + @ProtoId(40) val bitappMsg: BitAppMsg? = null, + @ProtoId(41) val openQqData: OpenQQData? = null, + @ProtoId(42) val apolloMsg: ApolloActMsg? = null, + @ProtoId(43) val groupPubAccInfo: GroupPubAccountInfo? = null, + @ProtoId(44) val blessMsg: BlessingMessage? = null, + @ProtoId(45) val srcMsg: SourceMsg? = null, + @ProtoId(46) val lolaMsg: LolaMsg? = null, + @ProtoId(47) val groupBusinessMsg: GroupBusinessMsg? = null, + @ProtoId(48) val msgWorkflowNotify: WorkflowNotifyMsg? = null, + @ProtoId(49) val patElem: PatsElem? = null, + @ProtoId(50) val groupPostElem: GroupPostElem? = null, + @ProtoId(51) val lightApp: LightAppElem? = null, + @ProtoId(52) val eimInfo: EIMInfo? = null, + @ProtoId(53) val commonElem: CommonElem? = null ) : ProtoBuf @Serializable internal class ElemFlags( - @SerialId(1) val flags1: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val businessData: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val flags1: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val businessData: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ElemFlags2( - @SerialId(1) val colorTextId: Int = 0, - @SerialId(2) val msgId: Long = 0L, - @SerialId(3) val whisperSessionId: Int = 0, - @SerialId(4) val pttChangeBit: Int = 0, - @SerialId(5) val vipStatus: Int = 0, - @SerialId(6) val compatibleId: Int = 0, - @SerialId(7) val insts: List? = null, - @SerialId(8) val msgRptCnt: Int = 0, - @SerialId(9) val srcInst: Inst? = null, - @SerialId(10) val longtitude: Int = 0, - @SerialId(11) val latitude: Int = 0, - @SerialId(12) val customFont: Int = 0, - @SerialId(13) val pcSupportDef: PcSupportDef? = null, - @SerialId(14) val crmFlags: Int = 0 + @ProtoId(1) val colorTextId: Int = 0, + @ProtoId(2) val msgId: Long = 0L, + @ProtoId(3) val whisperSessionId: Int = 0, + @ProtoId(4) val pttChangeBit: Int = 0, + @ProtoId(5) val vipStatus: Int = 0, + @ProtoId(6) val compatibleId: Int = 0, + @ProtoId(7) val insts: List? = null, + @ProtoId(8) val msgRptCnt: Int = 0, + @ProtoId(9) val srcInst: Inst? = null, + @ProtoId(10) val longtitude: Int = 0, + @ProtoId(11) val latitude: Int = 0, + @ProtoId(12) val customFont: Int = 0, + @ProtoId(13) val pcSupportDef: PcSupportDef? = null, + @ProtoId(14) val crmFlags: Int = 0 ) : ProtoBuf { @Serializable internal class Inst( - @SerialId(1) val appId: Int = 0, - @SerialId(2) val instId: Int = 0 + @ProtoId(1) val appId: Int = 0, + @ProtoId(2) val instId: Int = 0 ) } @Serializable internal class ExtraInfo( - @SerialId(1) val nick: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val groupCard: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val level: Int = 0, - @SerialId(4) val flags: Int = 0, - @SerialId(5) val groupMask: Int = 0, - @SerialId(6) val msgTailId: Int = 0, - @SerialId(7) val senderTitle: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val apnsTips: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val uin: Long = 0L, - @SerialId(10) val msgStateFlag: Int = 0, - @SerialId(11) val apnsSoundType: Int = 0, - @SerialId(12) val newGroupFlag: Int = 0 + @ProtoId(1) val nick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val groupCard: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val level: Int = 0, + @ProtoId(4) val flags: Int = 0, + @ProtoId(5) val groupMask: Int = 0, + @ProtoId(6) val msgTailId: Int = 0, + @ProtoId(7) val senderTitle: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val apnsTips: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val uin: Long = 0L, + @ProtoId(10) val msgStateFlag: Int = 0, + @ProtoId(11) val apnsSoundType: Int = 0, + @ProtoId(12) val newGroupFlag: Int = 0 ) : ProtoBuf @Serializable internal class Face( - @SerialId(1) val index: Int = 0, - @SerialId(2) val old: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(11) val buf: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val index: Int = 0, + @ProtoId(2) val old: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val buf: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class FSJMessageElem( - @SerialId(1) val msgType: Int = 0 + @ProtoId(1) val msgType: Int = 0 ) : ProtoBuf @Serializable internal class FunFace( - @SerialId(1) val msgTurntable: Turntable? = null, - @SerialId(2) val msgBomb: Bomb? = null + @ProtoId(1) val msgTurntable: Turntable? = null, + @ProtoId(2) val msgBomb: Bomb? = null ) { @Serializable internal class Bomb( - @SerialId(1) val boolBurst: Boolean = false + @ProtoId(1) val boolBurst: Boolean = false ) @Serializable internal class Turntable( - @SerialId(1) val uint64UinList: List? = null, - @SerialId(2) val hitUin: Long = 0L, - @SerialId(3) val hitUinNick: String = "" + @ProtoId(1) val uint64UinList: List? = null, + @ProtoId(2) val hitUin: Long = 0L, + @ProtoId(3) val hitUinNick: String = "" ) } @Serializable internal class GeneralFlags( - @SerialId(1) val bubbleDiyTextId: Int = 0, - @SerialId(2) val groupFlagNew: Int = 0, - @SerialId(3) val uin: Long = 0L, - @SerialId(4) val rpId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val prpFold: Int = 0, - @SerialId(6) val longTextFlag: Int = 0, - @SerialId(7) val longTextResid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val groupType: Int = 0, - @SerialId(9) val toUinFlag: Int = 0, - @SerialId(10) val glamourLevel: Int = 0, - @SerialId(11) val memberLevel: Int = 0, - @SerialId(12) val groupRankSeq: Long = 0L, - @SerialId(13) val olympicTorch: Int = 0, - @SerialId(14) val babyqGuideMsgCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(15) val uin32ExpertFlag: Int = 0, - @SerialId(16) val bubbleSubId: Int = 0, - @SerialId(17) val pendantId: Long = 0L, - @SerialId(18) val rpIndex: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY // 78 00 F8 01 00 C8 02 00 + @ProtoId(1) val bubbleDiyTextId: Int = 0, + @ProtoId(2) val groupFlagNew: Int = 0, + @ProtoId(3) val uin: Long = 0L, + @ProtoId(4) val rpId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val prpFold: Int = 0, + @ProtoId(6) val longTextFlag: Int = 0, + @ProtoId(7) val longTextResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val groupType: Int = 0, + @ProtoId(9) val toUinFlag: Int = 0, + @ProtoId(10) val glamourLevel: Int = 0, + @ProtoId(11) val memberLevel: Int = 0, + @ProtoId(12) val groupRankSeq: Long = 0L, + @ProtoId(13) val olympicTorch: Int = 0, + @ProtoId(14) val babyqGuideMsgCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(15) val uin32ExpertFlag: Int = 0, + @ProtoId(16) val bubbleSubId: Int = 0, + @ProtoId(17) val pendantId: Long = 0L, + @ProtoId(18) val rpIndex: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(19) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY // 78 00 F8 01 00 C8 02 00 ) : ProtoBuf @Serializable internal class GroupBusinessMsg( - @SerialId(1) val flags: Int = 0, - @SerialId(2) val headUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val headClkUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val nick: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val nickColor: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val rank: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val rankColor: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val rankBgcolor: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val flags: Int = 0, + @ProtoId(2) val headUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val headClkUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val nick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val nickColor: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val rank: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val rankColor: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val rankBgcolor: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupFile( - @SerialId(1) val filename: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val fileSize: Long = 0L, - @SerialId(3) val fileId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val batchId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val mark: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val sequence: Long = 0L, - @SerialId(8) val batchItemId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val feedMsgTime: Int = 0, - @SerialId(10) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val filename: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val fileSize: Long = 0L, + @ProtoId(3) val fileId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val batchId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val mark: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val sequence: Long = 0L, + @ProtoId(8) val batchItemId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val feedMsgTime: Int = 0, + @ProtoId(10) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupPostElem( - @SerialId(1) val transType: Int = 0, - @SerialId(2) val transMsg: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val transType: Int = 0, + @ProtoId(2) val transMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupPubAccountInfo( - @SerialId(1) val pubAccount: Long = 0L + @ProtoId(1) val pubAccount: Long = 0L ) : ProtoBuf @Serializable internal class LifeOnlineAccount( - @SerialId(1) val uniqueId: Long = 0L, - @SerialId(2) val op: Int = 0, - @SerialId(3) val showTime: Int = 0, - @SerialId(4) val report: Int = 0, - @SerialId(5) val ack: Int = 0, - @SerialId(6) val bitmap: Long = 0L, - @SerialId(7) val gdtImpData: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val gdtCliData: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val viewId: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val uniqueId: Long = 0L, + @ProtoId(2) val op: Int = 0, + @ProtoId(3) val showTime: Int = 0, + @ProtoId(4) val report: Int = 0, + @ProtoId(5) val ack: Int = 0, + @ProtoId(6) val bitmap: Long = 0L, + @ProtoId(7) val gdtImpData: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val gdtCliData: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val viewId: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class LightAppElem( - @SerialId(1) val data: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val data: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val msgResid: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class LocationInfo( - @SerialId(1) val longitude: Double = 0.0, - @SerialId(2) val latitude: Double = 0.0, - @SerialId(3) val desc: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val longitude: Double = 0.0, + @ProtoId(2) val latitude: Double = 0.0, + @ProtoId(3) val desc: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class LolaMsg( - @SerialId(1) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val encodeContent: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val longMsgUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val downloadKey: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val encodeContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val longMsgUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val downloadKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class LowVersionTips( - @SerialId(1) val businessId: Int = 0, - @SerialId(2) val sessionType: Int = 0, - @SerialId(3) val sessionUin: Long = 0L, - @SerialId(4) val senderUin: Long = 0L, - @SerialId(5) val text: String = "" + @ProtoId(1) val businessId: Int = 0, + @ProtoId(2) val sessionType: Int = 0, + @ProtoId(3) val sessionUin: Long = 0L, + @ProtoId(4) val senderUin: Long = 0L, + @ProtoId(5) val text: String = "" ) : ProtoBuf @Serializable internal class MarketFace( - @SerialId(1) val faceName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val itemType: Int = 0, - @SerialId(3) val faceInfo: Int = 0, - @SerialId(4) val faceId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val tabId: Int = 0, - @SerialId(6) val subType: Int = 0, - @SerialId(7) val key: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val param: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val mediaType: Int = 0, - @SerialId(10) val imageWidth: Int = 0, - @SerialId(11) val imageHeight: Int = 0, - @SerialId(12) val mobileparam: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(13) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val faceName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val itemType: Int = 0, + @ProtoId(3) val faceInfo: Int = 0, + @ProtoId(4) val faceId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val tabId: Int = 0, + @ProtoId(6) val subType: Int = 0, + @ProtoId(7) val key: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val param: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val mediaType: Int = 0, + @ProtoId(10) val imageWidth: Int = 0, + @ProtoId(11) val imageHeight: Int = 0, + @ProtoId(12) val mobileparam: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(13) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MarketTrans( - @SerialId(1) val int32Flag: Int = 0, - @SerialId(2) val xml: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val ability: Int = 0, - @SerialId(5) val minAbility: Int = 0 + @ProtoId(1) val int32Flag: Int = 0, + @ProtoId(2) val xml: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val ability: Int = 0, + @ProtoId(5) val minAbility: Int = 0 ) : ProtoBuf @Serializable internal class MsgBody( - @SerialId(1) val richText: RichText = RichText(), - @SerialId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val msgEncryptContent: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val richText: RichText = RichText(), + @ProtoId(2) val msgContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgEncryptContent: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgBodySubtype4( - @SerialId(1) val msgNotOnlineFile: NotOnlineFile? = null, - @SerialId(2) val msgTime: Int = 0 + @ProtoId(1) val msgNotOnlineFile: NotOnlineFile? = null, + @ProtoId(2) val msgTime: Int = 0 ) : ProtoBuf @Serializable internal class NearByMessageType( - @SerialId(1) val type: Int = 0, - @SerialId(2) val identifyType: Int = 0 + @ProtoId(1) val type: Int = 0, + @ProtoId(2) val identifyType: Int = 0 ) : ProtoBuf @Serializable internal class NotOnlineFile( - @SerialId(1) val fileType: Int = 0, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val fileName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val fileSize: Long = 0L, - @SerialId(7) val note: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val reserved: Int = 0, - @SerialId(9) val subcmd: Int = 0, - @SerialId(10) val microCloud: Int = 0, - @SerialId(11) val bytesFileUrls: List? = listOf(), - @SerialId(12) val downloadFlag: Int = 0, - @SerialId(50) val dangerEvel: Int = 0, - @SerialId(51) val lifeTime: Int = 0, - @SerialId(52) val uploadTime: Int = 0, - @SerialId(53) val absFileType: Int = 0, - @SerialId(54) val clientType: Int = 0, - @SerialId(55) val expireTime: Int = 0, - @SerialId(56) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val fileType: Int = 0, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val fileName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val fileSize: Long = 0L, + @ProtoId(7) val note: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val reserved: Int = 0, + @ProtoId(9) val subcmd: Int = 0, + @ProtoId(10) val microCloud: Int = 0, + @ProtoId(11) val bytesFileUrls: List? = listOf(), + @ProtoId(12) val downloadFlag: Int = 0, + @ProtoId(50) val dangerEvel: Int = 0, + @ProtoId(51) val lifeTime: Int = 0, + @ProtoId(52) val uploadTime: Int = 0, + @ProtoId(53) val absFileType: Int = 0, + @ProtoId(54) val clientType: Int = 0, + @ProtoId(55) val expireTime: Int = 0, + @ProtoId(56) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class NotOnlineImage( - @SerialId(1) val filePath: String = "", - @SerialId(2) val fileLen: Int = 0, - @SerialId(3) val downloadPath: String = "", - @SerialId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val imgType: Int = 0, - @SerialId(6) val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val picMd5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val picHeight: Int = 0, - @SerialId(9) val picWidth: Int = 0, - @SerialId(10) val resId: String = "", - @SerialId(11) val flag: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val thumbUrl: String = "", - @SerialId(13) val original: Int = 0, - @SerialId(14) val bigUrl: String = "", - @SerialId(15) val origUrl: String = "", - @SerialId(16) val bizType: Int = 0, - @SerialId(17) val result: Int = 0, - @SerialId(18) val index: Int = 0, - @SerialId(19) val opFaceBuf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20) val oldPicMd5: Boolean = false, - @SerialId(21) val thumbWidth: Int = 0, - @SerialId(22) val thumbHeight: Int = 0, - @SerialId(23) val fileId: Int = 0, - @SerialId(24) val showLen: Int = 0, - @SerialId(25) val downloadLen: Int = 0, - @SerialId(26) val _400Url: String = "", - @SerialId(27) val _400Width: Int = 0, - @SerialId(28) val _400Height: Int = 0, - @SerialId(29) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val filePath: String = "", + @ProtoId(2) val fileLen: Int = 0, + @ProtoId(3) val downloadPath: String = "", + @ProtoId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val imgType: Int = 0, + @ProtoId(6) val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val picMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val picHeight: Int = 0, + @ProtoId(9) val picWidth: Int = 0, + @ProtoId(10) val resId: String = "", + @ProtoId(11) val flag: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val thumbUrl: String = "", + @ProtoId(13) val original: Int = 0, + @ProtoId(14) val bigUrl: String = "", + @ProtoId(15) val origUrl: String = "", + @ProtoId(16) val bizType: Int = 0, + @ProtoId(17) val result: Int = 0, + @ProtoId(18) val index: Int = 0, + @ProtoId(19) val opFaceBuf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20) val oldPicMd5: Boolean = false, + @ProtoId(21) val thumbWidth: Int = 0, + @ProtoId(22) val thumbHeight: Int = 0, + @ProtoId(23) val fileId: Int = 0, + @ProtoId(24) val showLen: Int = 0, + @ProtoId(25) val downloadLen: Int = 0, + @ProtoId(26) val _400Url: String = "", + @ProtoId(27) val _400Width: Int = 0, + @ProtoId(28) val _400Height: Int = 0, + @ProtoId(29) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable // 非官方. internal data class PbReserve( - @SerialId(1) val unknown1: Int = 1, - @SerialId(2) val unknown2: Int = 0, - @SerialId(6) val unknown3: Int = 0, - @SerialId(8) val hint: String = "[动画表情]", - @SerialId(10) val unknown5: Int = 0, - @SerialId(15) val unknwon6: Int = 5 + @ProtoId(1) val unknown1: Int = 1, + @ProtoId(2) val unknown2: Int = 0, + @ProtoId(6) val unknown3: Int = 0, + @ProtoId(8) val hint: String = "[动画表情]", + @ProtoId(10) val unknown5: Int = 0, + @ProtoId(15) val unknwon6: Int = 5 ) : ProtoBuf { companion object { val DEFAULT: ByteArray = PbReserve().toByteArray(serializer()) @@ -679,292 +679,292 @@ internal class ImMsgBody : ProtoBuf { @Serializable internal class OnlineImage( - @SerialId(1) val guid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val filePath: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val guid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val filePath: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class OpenQQData( - @SerialId(1) val carQqData: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val carQqData: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PatsElem( - @SerialId(1) val patType: Int = 0, - @SerialId(2) val patCount: Int = 0 + @ProtoId(1) val patType: Int = 0, + @ProtoId(2) val patCount: Int = 0 ) : ProtoBuf @Serializable internal class PcSupportDef( - @SerialId(1) val pcPtlBegin: Int = 0, - @SerialId(2) val pcPtlEnd: Int = 0, - @SerialId(3) val macPtlBegin: Int = 0, - @SerialId(4) val macPtlEnd: Int = 0, - @SerialId(5) val ptlsSupport: List? = null, - @SerialId(6) val ptlsNotSupport: List? = null + @ProtoId(1) val pcPtlBegin: Int = 0, + @ProtoId(2) val pcPtlEnd: Int = 0, + @ProtoId(3) val macPtlBegin: Int = 0, + @ProtoId(4) val macPtlEnd: Int = 0, + @ProtoId(5) val ptlsSupport: List? = null, + @ProtoId(6) val ptlsNotSupport: List? = null ) : ProtoBuf @Serializable internal class Ptt( - @SerialId(1) val fileType: Int = 0, - @SerialId(2) val srcUin: Long = 0L, - @SerialId(3) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val fileName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val fileSize: Int = 0, - @SerialId(7) val reserve: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val fileId: Int = 0, - @SerialId(9) val serverIp: Int = 0, - @SerialId(10) val serverPort: Int = 0, - @SerialId(11) val boolValid: Boolean = false, - @SerialId(12) val signature: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(13) val shortcut: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(14) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(15) val magicPttIndex: Int = 0, - @SerialId(16) val voiceSwitch: Int = 0, - @SerialId(17) val pttUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(18) val groupFileKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(19) val time: Int = 0, - @SerialId(20) val downPara: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(29) val format: Int = 0, - @SerialId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(31) val bytesPttUrls: List? = listOf(), - @SerialId(32) val downloadFlag: Int = 0 + @ProtoId(1) val fileType: Int = 0, + @ProtoId(2) val srcUin: Long = 0L, + @ProtoId(3) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val fileName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val fileSize: Int = 0, + @ProtoId(7) val reserve: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val fileId: Int = 0, + @ProtoId(9) val serverIp: Int = 0, + @ProtoId(10) val serverPort: Int = 0, + @ProtoId(11) val boolValid: Boolean = false, + @ProtoId(12) val signature: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(13) val shortcut: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(14) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(15) val magicPttIndex: Int = 0, + @ProtoId(16) val voiceSwitch: Int = 0, + @ProtoId(17) val pttUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(18) val groupFileKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(19) val time: Int = 0, + @ProtoId(20) val downPara: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(29) val format: Int = 0, + @ProtoId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(31) val bytesPttUrls: List? = listOf(), + @ProtoId(32) val downloadFlag: Int = 0 ) : ProtoBuf @Serializable internal class PubAccInfo( - @SerialId(1) val isInterNum: Int = 0, - @SerialId(2) val ingMsgTemplateId: String = "", - @SerialId(3) val ingLongMsgUrl: String = "", - @SerialId(4) val downloadKey: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val isInterNum: Int = 0, + @ProtoId(2) val ingMsgTemplateId: String = "", + @ProtoId(3) val ingLongMsgUrl: String = "", + @ProtoId(4) val downloadKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PubAccount( - @SerialId(1) val buf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val pubAccountUin: Long = 0L + @ProtoId(1) val buf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val pubAccountUin: Long = 0L ) : ProtoBuf @Serializable internal class PubGroup( - @SerialId(1) val nickname: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val gender: Int = 0, - @SerialId(3) val age: Int = 0, - @SerialId(4) val distance: Int = 0 + @ProtoId(1) val nickname: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val gender: Int = 0, + @ProtoId(3) val age: Int = 0, + @ProtoId(4) val distance: Int = 0 ) : ProtoBuf @Serializable internal class QQLiveOld( - @SerialId(1) val subCmd: Int = 0, - @SerialId(2) val showText: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val param: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val introduce: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val subCmd: Int = 0, + @ProtoId(2) val showText: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val param: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val introduce: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class QQWalletAioBody( - @SerialId(1) val senduin: Long = 0L, - @SerialId(2) val sender: QQWalletAioElem? = null, - @SerialId(3) val receiver: QQWalletAioElem? = null, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(4) val sint32Channelid: Int = 0, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(5) val sint32Templateid: Int = 0, - @SerialId(6) val resend: Int = 0, - @SerialId(7) val msgPriority: Int = 0, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(8) val sint32Redtype: Int = 0, - @SerialId(9) val billno: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val authkey: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(11) val sint32Sessiontype: Int = 0, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(12) val sint32Msgtype: Int = 0, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(13) val sint32Envelopeid: Int = 0, - @SerialId(14) val name: ByteArray = EMPTY_BYTE_ARRAY, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(15) val sint32Conftype: Int = 0, - @ProtoType(ProtoNumberType.SIGNED) @SerialId(16) val sint32MsgFrom: Int = 0, - @SerialId(17) val pcBody: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(18) val ingIndex: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(19) val redchannel: Int = 0, - @SerialId(20) val grapUin: List? = null, - @SerialId(21) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val senduin: Long = 0L, + @ProtoId(2) val sender: QQWalletAioElem? = null, + @ProtoId(3) val receiver: QQWalletAioElem? = null, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(4) val sint32Channelid: Int = 0, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(5) val sint32Templateid: Int = 0, + @ProtoId(6) val resend: Int = 0, + @ProtoId(7) val msgPriority: Int = 0, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(8) val sint32Redtype: Int = 0, + @ProtoId(9) val billno: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val authkey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(11) val sint32Sessiontype: Int = 0, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(12) val sint32Msgtype: Int = 0, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(13) val sint32Envelopeid: Int = 0, + @ProtoId(14) val name: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(15) val sint32Conftype: Int = 0, + @ProtoType(ProtoNumberType.SIGNED) @ProtoId(16) val sint32MsgFrom: Int = 0, + @ProtoId(17) val pcBody: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(18) val ingIndex: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(19) val redchannel: Int = 0, + @ProtoId(20) val grapUin: List? = null, + @ProtoId(21) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class QQWalletAioElem( - @SerialId(1) val background: Int = 0, - @SerialId(2) val icon: Int = 0, - @SerialId(3) val title: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val subtitle: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val content: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val linkurl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val blackstripe: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val notice: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val titleColor: Int = 0, - @SerialId(10) val subtitleColor: Int = 0, - @SerialId(11) val actionsPriority: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(13) val nativeIos: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(14) val nativeAndroid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(15) val iconurl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(16) val contentColor: Int = 0, - @SerialId(17) val contentBgcolor: Int = 0, - @SerialId(18) val aioImageLeft: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(19) val aioImageRight: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20) val cftImage: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(21) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val background: Int = 0, + @ProtoId(2) val icon: Int = 0, + @ProtoId(3) val title: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val subtitle: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val content: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val linkurl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val blackstripe: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val notice: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val titleColor: Int = 0, + @ProtoId(10) val subtitleColor: Int = 0, + @ProtoId(11) val actionsPriority: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(13) val nativeIos: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(14) val nativeAndroid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(15) val iconurl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(16) val contentColor: Int = 0, + @ProtoId(17) val contentBgcolor: Int = 0, + @ProtoId(18) val aioImageLeft: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(19) val aioImageRight: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20) val cftImage: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(21) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class QQWalletMsg( - @SerialId(1) val aioBody: QQWalletAioBody? = null + @ProtoId(1) val aioBody: QQWalletAioBody? = null ) : ProtoBuf @Serializable internal class RedBagInfo( - @SerialId(1) val redbagType: Int = 0 + @ProtoId(1) val redbagType: Int = 0 ) : ProtoBuf @Serializable internal class RichMsg( - @SerialId(1) val template1: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val serviceId: Int = 0, - @SerialId(3) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val rand: Int = 0, - @SerialId(5) val seq: Int = 0, - @SerialId(6) val flags: Int = 0 + @ProtoId(1) val template1: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val serviceId: Int = 0, + @ProtoId(3) val msgResid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val rand: Int = 0, + @ProtoId(5) val seq: Int = 0, + @ProtoId(6) val flags: Int = 0 ) : ProtoBuf @Serializable internal class RichText( - @SerialId(1) val attr: Attr? = null, - @SerialId(2) val elems: MutableList = mutableListOf(), - @SerialId(3) val notOnlineFile: NotOnlineFile? = null, - @SerialId(4) val ptt: Ptt? = null, - @SerialId(5) val tmpPtt: TmpPtt? = null, - @SerialId(6) val trans211TmpMsg: Trans211TmpMsg? = null + @ProtoId(1) val attr: Attr? = null, + @ProtoId(2) val elems: MutableList = mutableListOf(), + @ProtoId(3) val notOnlineFile: NotOnlineFile? = null, + @ProtoId(4) val ptt: Ptt? = null, + @ProtoId(5) val tmpPtt: TmpPtt? = null, + @ProtoId(6) val trans211TmpMsg: Trans211TmpMsg? = null ) : ProtoBuf @Serializable internal class SecretFileMsg( - @SerialId(1) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val fromUin: Long = 0L, - @SerialId(3) val toUin: Long = 0L, - @SerialId(4) val status: Int = 0, - @SerialId(5) val ttl: Int = 0, - @SerialId(6) val type: Int = 0, - @SerialId(7) val encryptPreheadLength: Int = 0, - @SerialId(8) val encryptType: Int = 0, - @SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val readTimes: Int = 0, - @SerialId(11) val leftTime: Int = 0, - @SerialId(12) val notOnlineImage: NotOnlineImage? = null, - @SerialId(13) val elemFlags2: ElemFlags2? = null, - @SerialId(14) val opertype: Int = 0, - @SerialId(15) val fromphonenum: String = "" + @ProtoId(1) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val fromUin: Long = 0L, + @ProtoId(3) val toUin: Long = 0L, + @ProtoId(4) val status: Int = 0, + @ProtoId(5) val ttl: Int = 0, + @ProtoId(6) val type: Int = 0, + @ProtoId(7) val encryptPreheadLength: Int = 0, + @ProtoId(8) val encryptType: Int = 0, + @ProtoId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val readTimes: Int = 0, + @ProtoId(11) val leftTime: Int = 0, + @ProtoId(12) val notOnlineImage: NotOnlineImage? = null, + @ProtoId(13) val elemFlags2: ElemFlags2? = null, + @ProtoId(14) val opertype: Int = 0, + @ProtoId(15) val fromphonenum: String = "" ) : ProtoBuf @Serializable internal class ShakeWindow( - @SerialId(1) val type: Int = 0, - @SerialId(2) val reserve: Int = 0, - @SerialId(3) val uin: Long = 0L + @ProtoId(1) val type: Int = 0, + @ProtoId(2) val reserve: Int = 0, + @ProtoId(3) val uin: Long = 0L ) : ProtoBuf @Serializable internal class SmallEmoji( - @SerialId(1) val packIdSum: Int = 0, - @SerialId(2) val imageType: Int = 0 + @ProtoId(1) val packIdSum: Int = 0, + @ProtoId(2) val imageType: Int = 0 ) : ProtoBuf @Serializable internal class SourceMsg( - @SerialId(1) val origSeqs: List? = null, - @SerialId(2) val senderUin: Long = 0L, - @SerialId(3) val time: Int = 0, - @SerialId(4) val flag: Int = 0, - @SerialId(5) val elems: List? = null, - @SerialId(6) val type: Int = 0, - @SerialId(7) val richMsg: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val srcMsg: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val toUin: Long = 0L, - @SerialId(11) val troopName: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val origSeqs: List? = null, + @ProtoId(2) val senderUin: Long = 0L, + @ProtoId(3) val time: Int = 0, + @ProtoId(4) val flag: Int = 0, + @ProtoId(5) val elems: List? = null, + @ProtoId(6) val type: Int = 0, + @ProtoId(7) val richMsg: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val srcMsg: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val toUin: Long = 0L, + @ProtoId(11) val troopName: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class Text( - @SerialId(1) val str: String = "", - @SerialId(2) val link: String = "", - @SerialId(3) val attr6Buf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val attr7Buf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(11) val buf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val str: String = "", + @ProtoId(2) val link: String = "", + @ProtoId(3) val attr6Buf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val attr7Buf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val buf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class TipsInfo( - @SerialId(1) val text: String = "" + @ProtoId(1) val text: String = "" ) : ProtoBuf @Serializable internal class TmpPtt( - @SerialId(1) val fileType: Int = 0, - @SerialId(2) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val fileName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val fileSize: Int = 0, - @SerialId(6) val pttTimes: Int = 0, - @SerialId(7) val userType: Int = 0, - @SerialId(8) val ptttransFlag: Int = 0, - @SerialId(9) val busiType: Int = 0, - @SerialId(10) val msgId: Long = 0L, - @SerialId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(31) val pttEncodeData: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val fileType: Int = 0, + @ProtoId(2) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val fileName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val fileSize: Int = 0, + @ProtoId(6) val pttTimes: Int = 0, + @ProtoId(7) val userType: Int = 0, + @ProtoId(8) val ptttransFlag: Int = 0, + @ProtoId(9) val busiType: Int = 0, + @ProtoId(10) val msgId: Long = 0L, + @ProtoId(30) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(31) val pttEncodeData: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class Trans211TmpMsg( - @SerialId(1) val msgBody: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val c2cCmd: Int = 0 + @ProtoId(1) val msgBody: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val c2cCmd: Int = 0 ) : ProtoBuf @Serializable internal class TransElem( - @SerialId(1) val elemType: Int = 0, - @SerialId(2) val elemValue: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val elemType: Int = 0, + @ProtoId(2) val elemValue: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class VideoFile( - @SerialId(1) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val fileName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val fileFormat: Int = 0, - @SerialId(5) val fileTime: Int = 0, - @SerialId(6) val fileSize: Int = 0, - @SerialId(7) val thumbWidth: Int = 0, - @SerialId(8) val thumbHeight: Int = 0, - @SerialId(9) val thumbFileMd5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val source: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(11) val thumbFileSize: Int = 0, - @SerialId(12) val busiType: Int = 0, - @SerialId(13) val fromChatType: Int = 0, - @SerialId(14) val toChatType: Int = 0, - @SerialId(15) val boolSupportProgressive: Boolean = false, - @SerialId(16) val fileWidth: Int = 0, - @SerialId(17) val fileHeight: Int = 0, - @SerialId(18) val subBusiType: Int = 0, - @SerialId(19) val videoAttr: Int = 0, - @SerialId(20) val bytesThumbFileUrls: List? = null, - @SerialId(21) val bytesVideoFileUrls: List? = null, - @SerialId(22) val thumbDownloadFlag: Int = 0, - @SerialId(23) val videoDownloadFlag: Int = 0, - @SerialId(24) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val fileName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val fileFormat: Int = 0, + @ProtoId(5) val fileTime: Int = 0, + @ProtoId(6) val fileSize: Int = 0, + @ProtoId(7) val thumbWidth: Int = 0, + @ProtoId(8) val thumbHeight: Int = 0, + @ProtoId(9) val thumbFileMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val source: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val thumbFileSize: Int = 0, + @ProtoId(12) val busiType: Int = 0, + @ProtoId(13) val fromChatType: Int = 0, + @ProtoId(14) val toChatType: Int = 0, + @ProtoId(15) val boolSupportProgressive: Boolean = false, + @ProtoId(16) val fileWidth: Int = 0, + @ProtoId(17) val fileHeight: Int = 0, + @ProtoId(18) val subBusiType: Int = 0, + @ProtoId(19) val videoAttr: Int = 0, + @ProtoId(20) val bytesThumbFileUrls: List? = null, + @ProtoId(21) val bytesVideoFileUrls: List? = null, + @ProtoId(22) val thumbDownloadFlag: Int = 0, + @ProtoId(23) val videoDownloadFlag: Int = 0, + @ProtoId(24) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class WorkflowNotifyMsg( - @SerialId(1) val extMsg: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val createUin: Long = 0L + @ProtoId(1) val extMsg: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val createUin: Long = 0L ) : ProtoBuf } @@ -972,140 +972,140 @@ internal class ImMsgBody : ProtoBuf { internal class ImMsgHead : ProtoBuf { @Serializable internal class C2CHead( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val fromUin: Long = 0L, - @SerialId(3) val ccType: Int = 0, - @SerialId(4) val ccCmd: Int = 0, - @SerialId(5) val authPicSig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val authSig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val authBuf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val serverTime: Int = 0, - @SerialId(9) val clientTime: Int = 0, - @SerialId(10) val rand: Int = 0, - @SerialId(11) val ingPhoneNumber: String = "" + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val fromUin: Long = 0L, + @ProtoId(3) val ccType: Int = 0, + @ProtoId(4) val ccCmd: Int = 0, + @ProtoId(5) val authPicSig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val authSig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val authBuf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val serverTime: Int = 0, + @ProtoId(9) val clientTime: Int = 0, + @ProtoId(10) val rand: Int = 0, + @ProtoId(11) val ingPhoneNumber: String = "" ) : ProtoBuf @Serializable internal class CSHead( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val command: Int = 0, - @SerialId(3) val seq: Int = 0, - @SerialId(4) val version: Int = 0, - @SerialId(5) val retryTimes: Int = 0, - @SerialId(6) val clientType: Int = 0, - @SerialId(7) val pubno: Int = 0, - @SerialId(8) val localid: Int = 0, - @SerialId(9) val timezone: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(10) val clientIp: Int = 0, - @SerialId(11) val clientPort: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(12) val connIp: Int = 0, - @SerialId(13) val connPort: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(14) val interfaceIp: Int = 0, - @SerialId(15) val interfacePort: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(16) val actualIp: Int = 0, - @SerialId(17) val flag: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(18) val timestamp: Int = 0, - @SerialId(19) val subcmd: Int = 0, - @SerialId(20) val result: Int = 0, - @SerialId(21) val appId: Int = 0, - @SerialId(22) val instanceId: Int = 0, - @SerialId(23) val sessionId: Long = 0L, - @SerialId(24) val idcId: Int = 0 + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val command: Int = 0, + @ProtoId(3) val seq: Int = 0, + @ProtoId(4) val version: Int = 0, + @ProtoId(5) val retryTimes: Int = 0, + @ProtoId(6) val clientType: Int = 0, + @ProtoId(7) val pubno: Int = 0, + @ProtoId(8) val localid: Int = 0, + @ProtoId(9) val timezone: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(10) val clientIp: Int = 0, + @ProtoId(11) val clientPort: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(12) val connIp: Int = 0, + @ProtoId(13) val connPort: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(14) val interfaceIp: Int = 0, + @ProtoId(15) val interfacePort: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(16) val actualIp: Int = 0, + @ProtoId(17) val flag: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(18) val timestamp: Int = 0, + @ProtoId(19) val subcmd: Int = 0, + @ProtoId(20) val result: Int = 0, + @ProtoId(21) val appId: Int = 0, + @ProtoId(22) val instanceId: Int = 0, + @ProtoId(23) val sessionId: Long = 0L, + @ProtoId(24) val idcId: Int = 0 ) : ProtoBuf @Serializable internal class DeltaHead( - @SerialId(1) val totalLen: Long = 0L, - @SerialId(2) val offset: Long = 0L, - @SerialId(3) val ackOffset: Long = 0L, - @SerialId(4) val cookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val ackCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val result: Int = 0, - @SerialId(7) val flags: Int = 0 + @ProtoId(1) val totalLen: Long = 0L, + @ProtoId(2) val offset: Long = 0L, + @ProtoId(3) val ackOffset: Long = 0L, + @ProtoId(4) val cookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val ackCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val result: Int = 0, + @ProtoId(7) val flags: Int = 0 ) : ProtoBuf @Serializable internal class Head( - @SerialId(1) val headType: Int = 0, - @SerialId(2) val msgCsHead: CSHead? = null, - @SerialId(3) val msgS2cHead: S2CHead? = null, - @SerialId(4) val msgHttpconnHead: HttpConnHead? = null, - @SerialId(5) val paintFlag: Int = 0, - @SerialId(6) val msgLoginSig: LoginSig? = null, - @SerialId(7) val msgDeltaHead: DeltaHead? = null, - @SerialId(8) val msgC2cHead: C2CHead? = null, - @SerialId(9) val msgSconnHead: SConnHead? = null, - @SerialId(10) val msgInstCtrl: InstCtrl? = null + @ProtoId(1) val headType: Int = 0, + @ProtoId(2) val msgCsHead: CSHead? = null, + @ProtoId(3) val msgS2cHead: S2CHead? = null, + @ProtoId(4) val msgHttpconnHead: HttpConnHead? = null, + @ProtoId(5) val paintFlag: Int = 0, + @ProtoId(6) val msgLoginSig: LoginSig? = null, + @ProtoId(7) val msgDeltaHead: DeltaHead? = null, + @ProtoId(8) val msgC2cHead: C2CHead? = null, + @ProtoId(9) val msgSconnHead: SConnHead? = null, + @ProtoId(10) val msgInstCtrl: InstCtrl? = null ) : ProtoBuf @Serializable internal class HttpConnHead( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val command: Int = 0, - @SerialId(3) val subCommand: Int = 0, - @SerialId(4) val seq: Int = 0, - @SerialId(5) val version: Int = 0, - @SerialId(6) val retryTimes: Int = 0, - @SerialId(7) val clientType: Int = 0, - @SerialId(8) val pubNo: Int = 0, - @SerialId(9) val localId: Int = 0, - @SerialId(10) val timeZone: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(11) val clientIp: Int = 0, - @SerialId(12) val clientPort: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(13) val qzhttpIp: Int = 0, - @SerialId(14) val qzhttpPort: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(15) val sppIp: Int = 0, - @SerialId(16) val sppPort: Int = 0, - @SerialId(17) val flag: Int = 0, - @SerialId(18) val key: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(19) val compressType: Int = 0, - @SerialId(20) val originSize: Int = 0, - @SerialId(21) val errorCode: Int = 0, - @SerialId(22) val msgRedirect: RedirectMsg? = null, - @SerialId(23) val commandId: Int = 0, - @SerialId(24) val serviceCmdid: Int = 0, - @SerialId(25) val msgOidbhead: TransOidbHead? = null + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val command: Int = 0, + @ProtoId(3) val subCommand: Int = 0, + @ProtoId(4) val seq: Int = 0, + @ProtoId(5) val version: Int = 0, + @ProtoId(6) val retryTimes: Int = 0, + @ProtoId(7) val clientType: Int = 0, + @ProtoId(8) val pubNo: Int = 0, + @ProtoId(9) val localId: Int = 0, + @ProtoId(10) val timeZone: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(11) val clientIp: Int = 0, + @ProtoId(12) val clientPort: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(13) val qzhttpIp: Int = 0, + @ProtoId(14) val qzhttpPort: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(15) val sppIp: Int = 0, + @ProtoId(16) val sppPort: Int = 0, + @ProtoId(17) val flag: Int = 0, + @ProtoId(18) val key: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(19) val compressType: Int = 0, + @ProtoId(20) val originSize: Int = 0, + @ProtoId(21) val errorCode: Int = 0, + @ProtoId(22) val msgRedirect: RedirectMsg? = null, + @ProtoId(23) val commandId: Int = 0, + @ProtoId(24) val serviceCmdid: Int = 0, + @ProtoId(25) val msgOidbhead: TransOidbHead? = null ) : ProtoBuf @Serializable internal class InstCtrl( - @SerialId(1) val msgSendToInst: List? = listOf(), - @SerialId(2) val msgExcludeInst: List? = listOf(), - @SerialId(3) val msgFromInst: InstInfo? = InstInfo() + @ProtoId(1) val msgSendToInst: List? = listOf(), + @ProtoId(2) val msgExcludeInst: List? = listOf(), + @ProtoId(3) val msgFromInst: InstInfo? = InstInfo() ) : ProtoBuf @Serializable internal class InstInfo( - @SerialId(1) val apppid: Int = 0, - @SerialId(2) val instid: Int = 0, - @SerialId(3) val platform: Int = 0, - @SerialId(10) val enumDeviceType: Int /* enum */ = 0 + @ProtoId(1) val apppid: Int = 0, + @ProtoId(2) val instid: Int = 0, + @ProtoId(3) val platform: Int = 0, + @ProtoId(10) val enumDeviceType: Int /* enum */ = 0 ) : ProtoBuf @Serializable internal class LoginSig( - @SerialId(1) val type: Int = 0, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val type: Int = 0, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RedirectMsg( - @ProtoType(ProtoNumberType.FIXED) @SerialId(1) val lastRedirectIp: Int = 0, - @SerialId(2) val lastRedirectPort: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(3) val redirectIp: Int = 0, - @SerialId(4) val redirectPort: Int = 0, - @SerialId(5) val redirectCount: Int = 0 + @ProtoType(ProtoNumberType.FIXED) @ProtoId(1) val lastRedirectIp: Int = 0, + @ProtoId(2) val lastRedirectPort: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(3) val redirectIp: Int = 0, + @ProtoId(4) val redirectPort: Int = 0, + @ProtoId(5) val redirectCount: Int = 0 ) : ProtoBuf @Serializable internal class S2CHead( - @SerialId(1) val subMsgtype: Int = 0, - @SerialId(2) val msgType: Int = 0, - @SerialId(3) val fromUin: Long = 0L, - @SerialId(4) val msgId: Int = 0, - @ProtoType(ProtoNumberType.FIXED) @SerialId(5) val relayIp: Int = 0, - @SerialId(6) val relayPort: Int = 0, - @SerialId(7) val toUin: Long = 0L + @ProtoId(1) val subMsgtype: Int = 0, + @ProtoId(2) val msgType: Int = 0, + @ProtoId(3) val fromUin: Long = 0L, + @ProtoId(4) val msgId: Int = 0, + @ProtoType(ProtoNumberType.FIXED) @ProtoId(5) val relayIp: Int = 0, + @ProtoId(6) val relayPort: Int = 0, + @ProtoId(7) val toUin: Long = 0L ) : ProtoBuf @Serializable @@ -1113,10 +1113,10 @@ internal class ImMsgHead : ProtoBuf { @Serializable internal class TransOidbHead( - @SerialId(1) val command: Int = 0, - @SerialId(2) val serviceType: Int = 0, - @SerialId(3) val result: Int = 0, - @SerialId(4) val errorMsg: String = "" + @ProtoId(1) val command: Int = 0, + @ProtoId(2) val serviceType: Int = 0, + @ProtoId(3) val result: Int = 0, + @ProtoId(4) val errorMsg: String = "" ) : ProtoBuf } @@ -1124,27 +1124,27 @@ internal class ImMsgHead : ProtoBuf { internal class ImReceipt : ProtoBuf { @Serializable internal class MsgInfo( - @SerialId(1) val fromUin: Long = 0L, - @SerialId(2) val toUin: Long = 0L, - @SerialId(3) val msgSeq: Int = 0, - @SerialId(4) val msgRandom: Int = 0 + @ProtoId(1) val fromUin: Long = 0L, + @ProtoId(2) val toUin: Long = 0L, + @ProtoId(3) val msgSeq: Int = 0, + @ProtoId(4) val msgRandom: Int = 0 ) : ProtoBuf @Serializable data internal class ReceiptInfo( - @SerialId(1) val readTime: Long = 0L + @ProtoId(1) val readTime: Long = 0L ) : ProtoBuf @Serializable internal class ReceiptReq( - @SerialId(1) val command: Int /* enum */ = 1, - @SerialId(2) val msgInfo: MsgInfo? = null + @ProtoId(1) val command: Int /* enum */ = 1, + @ProtoId(2) val msgInfo: MsgInfo? = null ) : ProtoBuf @Serializable internal data class ReceiptResp( - @SerialId(1) val command: Int /* enum */ = 1, - @SerialId(2) val receiptInfo: ReceiptInfo? = null + @ProtoId(1) val command: Int /* enum */ = 1, + @ProtoId(2) val receiptInfo: ReceiptInfo? = null ) : ProtoBuf } @@ -1152,37 +1152,37 @@ internal class ImReceipt : ProtoBuf { internal class ObjMsg : ProtoBuf { @Serializable internal class MsgContentInfo( - @SerialId(1) val contentInfoId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val msgFile: MsgFile? = null + @ProtoId(1) val contentInfoId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val msgFile: MsgFile? = null ) : ProtoBuf { @Serializable internal class MsgFile( - @SerialId(1) val busId: Int = 0, - @SerialId(2) val filePath: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val fileSize: Long = 0L, - @SerialId(4) val fileName: String = "", - @SerialId(5) val int64DeadTime: Long = 0L, - @SerialId(6) val fileSha1: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val ext: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val busId: Int = 0, + @ProtoId(2) val filePath: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val fileSize: Long = 0L, + @ProtoId(4) val fileName: String = "", + @ProtoId(5) val int64DeadTime: Long = 0L, + @ProtoId(6) val fileSha1: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val ext: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class MsgPic( - @SerialId(1) val smallPicUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val originalPicUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val localPicId: Int = 0 + @ProtoId(1) val smallPicUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val originalPicUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val localPicId: Int = 0 ) : ProtoBuf @Serializable internal class ObjMsg( - @SerialId(1) val msgType: Int = 0, - @SerialId(2) val title: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val bytesAbstact: List? = null, - @SerialId(5) val titleExt: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val msgPic: List? = null, - @SerialId(7) val msgContentInfo: List? = null, - @SerialId(8) val reportIdShow: Int = 0 + @ProtoId(1) val msgType: Int = 0, + @ProtoId(2) val title: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val bytesAbstact: List? = null, + @ProtoId(5) val titleExt: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val msgPic: List? = null, + @ProtoId(7) val msgContentInfo: List? = null, + @ProtoId(8) val reportIdShow: Int = 0 ) : ProtoBuf } @@ -1190,111 +1190,111 @@ internal class ObjMsg : ProtoBuf { internal class Submsgtype0xc7 : ProtoBuf { @Serializable internal class RelationalChainChange( - @SerialId(1) val appid: Long = 0L, - @SerialId(2) val srcUin: Long = 0L, - @SerialId(3) val dstUin: Long = 0L, - @SerialId(4) val changeType: Int /* enum */ = 1, - @SerialId(5) val msgRelationalChainInfoOld: RelationalChainInfo? = null, - @SerialId(6) val msgRelationalChainInfoNew: RelationalChainInfo? = null, - @SerialId(7) val msgToDegradeInfo: ToDegradeInfo? = null, - @SerialId(20) val relationalChainInfos: List? = null, - @SerialId(100) val uint32FeatureId: List? = null + @ProtoId(1) val appid: Long = 0L, + @ProtoId(2) val srcUin: Long = 0L, + @ProtoId(3) val dstUin: Long = 0L, + @ProtoId(4) val changeType: Int /* enum */ = 1, + @ProtoId(5) val msgRelationalChainInfoOld: RelationalChainInfo? = null, + @ProtoId(6) val msgRelationalChainInfoNew: RelationalChainInfo? = null, + @ProtoId(7) val msgToDegradeInfo: ToDegradeInfo? = null, + @ProtoId(20) val relationalChainInfos: List? = null, + @ProtoId(100) val uint32FeatureId: List? = null ) : ProtoBuf @Serializable internal class FriendShipFlagNotify( - @SerialId(1) val dstUin: Long = 0L, - @SerialId(2) val level1: Int = 0, - @SerialId(3) val level2: Int = 0, - @SerialId(4) val continuityDays: Int = 0, - @SerialId(5) val chatFlag: Int = 0, - @SerialId(6) val lastChatTime: Long = 0L, - @SerialId(7) val notifyTime: Long = 0L, - @SerialId(8) val seq: Int = 0 + @ProtoId(1) val dstUin: Long = 0L, + @ProtoId(2) val level1: Int = 0, + @ProtoId(3) val level2: Int = 0, + @ProtoId(4) val continuityDays: Int = 0, + @ProtoId(5) val chatFlag: Int = 0, + @ProtoId(6) val lastChatTime: Long = 0L, + @ProtoId(7) val notifyTime: Long = 0L, + @ProtoId(8) val seq: Int = 0 ) : ProtoBuf @Serializable internal class ToDegradeItem( - @SerialId(1) val type: Int /* enum */ = 1, - @SerialId(2) val oldLevel: Int = 0, - @SerialId(3) val newLevel: Int = 0, - @SerialId(11) val continuityDays: Int = 0, - @SerialId(12) val lastActionTime: Long = 0L + @ProtoId(1) val type: Int /* enum */ = 1, + @ProtoId(2) val oldLevel: Int = 0, + @ProtoId(3) val newLevel: Int = 0, + @ProtoId(11) val continuityDays: Int = 0, + @ProtoId(12) val lastActionTime: Long = 0L ) : ProtoBuf @Serializable internal class RelationalChainInfo( - @SerialId(1) val type: Int /* enum */ = 1, - @SerialId(2) val attr: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(1002) val intimateInfo: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(91001) val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(101001) val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val type: Int /* enum */ = 1, + @ProtoId(2) val attr: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(1002) val intimateInfo: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(91001) val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(101001) val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ForwardBody( - @SerialId(1) val notifyType: Int = 0, - @SerialId(2) val opType: Int = 0, - @SerialId(3000) val msgHotFriendNotify: HotFriendNotify? = null, - @SerialId(4000) val msgRelationalChainChange: RelationalChainChange? = null + @ProtoId(1) val notifyType: Int = 0, + @ProtoId(2) val opType: Int = 0, + @ProtoId(3000) val msgHotFriendNotify: HotFriendNotify? = null, + @ProtoId(4000) val msgRelationalChainChange: RelationalChainChange? = null ) : ProtoBuf @Serializable internal class HotFriendNotify( - @SerialId(1) val dstUin: Long = 0L, - @SerialId(2) val praiseHotLevel: Int = 0, - @SerialId(3) val chatHotLevel: Int = 0, - @SerialId(4) val praiseHotDays: Int = 0, - @SerialId(5) val chatHotDays: Int = 0, - @SerialId(6) val closeLevel: Int = 0, - @SerialId(7) val closeDays: Int = 0, - @SerialId(8) val praiseFlag: Int = 0, - @SerialId(9) val chatFlag: Int = 0, - @SerialId(10) val closeFlag: Int = 0, - @SerialId(11) val notifyTime: Long = 0L, - @SerialId(12) val lastPraiseTime: Long = 0L, - @SerialId(13) val lastChatTime: Long = 0L, - @SerialId(14) val qzoneHotLevel: Int = 0, - @SerialId(15) val qzoneHotDays: Int = 0, - @SerialId(16) val qzoneFlag: Int = 0, - @SerialId(17) val lastQzoneTime: Long = 0L, - @SerialId(51) val showRecheckEntry: Int = 0, - @SerialId(52) val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(100) val loverFlag: Int = 0, - @SerialId(200) val keyHotLevel: Int = 0, - @SerialId(201) val keyHotDays: Int = 0, - @SerialId(202) val keyFlag: Int = 0, - @SerialId(203) val lastKeyTime: Long = 0L, - @SerialId(204) val keyWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(205) val keyTransFlag: Int = 0, - @SerialId(206) val loverKeyBusinessType: Int = 0, - @SerialId(207) val loverKeySubBusinessType: Int = 0, - @SerialId(208) val loverKeyMainWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(209) val loverKeyLinkWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(300) val boatLevel: Int = 0, - @SerialId(301) val boatDays: Int = 0, - @SerialId(302) val boatFlag: Int = 0, - @SerialId(303) val lastBoatTime: Int = 0, - @SerialId(304) val boatWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(400) val notifyType: Int = 0, - @SerialId(401) val msgFriendshipFlagNotify: FriendShipFlagNotify? = null + @ProtoId(1) val dstUin: Long = 0L, + @ProtoId(2) val praiseHotLevel: Int = 0, + @ProtoId(3) val chatHotLevel: Int = 0, + @ProtoId(4) val praiseHotDays: Int = 0, + @ProtoId(5) val chatHotDays: Int = 0, + @ProtoId(6) val closeLevel: Int = 0, + @ProtoId(7) val closeDays: Int = 0, + @ProtoId(8) val praiseFlag: Int = 0, + @ProtoId(9) val chatFlag: Int = 0, + @ProtoId(10) val closeFlag: Int = 0, + @ProtoId(11) val notifyTime: Long = 0L, + @ProtoId(12) val lastPraiseTime: Long = 0L, + @ProtoId(13) val lastChatTime: Long = 0L, + @ProtoId(14) val qzoneHotLevel: Int = 0, + @ProtoId(15) val qzoneHotDays: Int = 0, + @ProtoId(16) val qzoneFlag: Int = 0, + @ProtoId(17) val lastQzoneTime: Long = 0L, + @ProtoId(51) val showRecheckEntry: Int = 0, + @ProtoId(52) val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(100) val loverFlag: Int = 0, + @ProtoId(200) val keyHotLevel: Int = 0, + @ProtoId(201) val keyHotDays: Int = 0, + @ProtoId(202) val keyFlag: Int = 0, + @ProtoId(203) val lastKeyTime: Long = 0L, + @ProtoId(204) val keyWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(205) val keyTransFlag: Int = 0, + @ProtoId(206) val loverKeyBusinessType: Int = 0, + @ProtoId(207) val loverKeySubBusinessType: Int = 0, + @ProtoId(208) val loverKeyMainWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(209) val loverKeyLinkWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(300) val boatLevel: Int = 0, + @ProtoId(301) val boatDays: Int = 0, + @ProtoId(302) val boatFlag: Int = 0, + @ProtoId(303) val lastBoatTime: Int = 0, + @ProtoId(304) val boatWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(400) val notifyType: Int = 0, + @ProtoId(401) val msgFriendshipFlagNotify: FriendShipFlagNotify? = null ) : ProtoBuf @Serializable internal class RelationalChainInfos( - @SerialId(1) val msgRelationalChainInfoOld: RelationalChainInfo? = null, - @SerialId(2) val msgRelationalChainInfoNew: RelationalChainInfo? = null + @ProtoId(1) val msgRelationalChainInfoOld: RelationalChainInfo? = null, + @ProtoId(2) val msgRelationalChainInfoNew: RelationalChainInfo? = null ) : ProtoBuf @Serializable internal class ToDegradeInfo( - @SerialId(1) val toDegradeItem: List? = null, - @SerialId(2) val nick: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val notifyTime: Long = 0L + @ProtoId(1) val toDegradeItem: List? = null, + @ProtoId(2) val nick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val notifyTime: Long = 0L ) : ProtoBuf @Serializable internal class MsgBody( - @SerialId(1) val msgModInfos: List? = null + @ProtoId(1) val msgModInfos: List? = null ) : ProtoBuf } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt index 4cda86784..13e2b0be6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgCommon.kt @@ -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? = 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? = 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 } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgRevokeUserDef.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgRevokeUserDef.kt new file mode 100644 index 000000000..61da3aa4a --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgRevokeUserDef.kt @@ -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? = null, + @ProtoId(3) val fileUuid: List = 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 = listOf() + ) : ProtoBuf +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt index 3007c6036..f0d48e18c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/MsgSvc.kt @@ -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.data.Packet import net.mamoe.mirai.qqandroid.io.ProtoBuf import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY @@ -19,141 +19,141 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY internal class MsgSvc : ProtoBuf { @Serializable internal class Grp( - @SerialId(1) val groupCode: Long = 0L + @ProtoId(1) val groupCode: Long = 0L ) : ProtoBuf @Serializable internal class PbGetMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val syncFlag: SyncFlag, - @SerialId(5) val uinPairMsgs: List? = null, - @SerialId(6) val bindUin: Long = 0L, - @SerialId(7) val msgRspType: Int = 0, - @SerialId(8) val pubAccountCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val isPartialSync: Boolean = false, - @SerialId(10) val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val syncCookie: ByteArray? = EMPTY_BYTE_ARRAY, + @ProtoId(4) val syncFlag: SyncFlag, + @ProtoId(5) val uinPairMsgs: List? = null, + @ProtoId(6) val bindUin: Long = 0L, + @ProtoId(7) val msgRspType: Int = 0, + @ProtoId(8) val pubAccountCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val isPartialSync: Boolean = false, + @ProtoId(10) val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbGroupMsgWithDrawReq( - @SerialId(1) val subCmd: Int = 0, - @SerialId(2) val groupType: Int = 0, - @SerialId(3) val groupCode: Long = 0L, - @SerialId(4) val msgList: List? = null, - @SerialId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val subCmd: Int = 0, + @ProtoId(2) val groupType: Int = 0, + @ProtoId(3) val groupCode: Long = 0L, + @ProtoId(4) val msgList: List? = null, + @ProtoId(5) val userdef: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf { @Serializable internal class MessageInfo( - @SerialId(1) val msgSeq: Int = 0, - @SerialId(2) val msgRandom: Int = 0, - @SerialId(3) val msgType: Int = 0 + @ProtoId(1) val msgSeq: Int = 0, + @ProtoId(2) val msgRandom: Int = 0, + @ProtoId(3) val msgType: Int = 0 ) } @Serializable internal class PbGroupReadedReportReq( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val lastReadSeq: Long = 0L + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val lastReadSeq: Long = 0L ) : ProtoBuf @Serializable internal class BusinessWPATmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val sigt: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val sigt: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class C2C( - @SerialId(1) val toUin: Long = 0L + @ProtoId(1) val toUin: Long = 0L ) : ProtoBuf @Serializable internal class PbGetGroupMsgReq( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val beginSeq: Long = 0L, - @SerialId(3) val endSeq: Long = 0L, - @SerialId(4) val filter: Int /* enum */ = 0, - @SerialId(5) val memberSeq: Long = 0L, - @SerialId(6) val publicGroup: Boolean = false, - @SerialId(7) val shieldFlag: Int = 0, - @SerialId(8) val saveTrafficFlag: Int = 0 + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val beginSeq: Long = 0L, + @ProtoId(3) val endSeq: Long = 0L, + @ProtoId(4) val filter: Int /* enum */ = 0, + @ProtoId(5) val memberSeq: Long = 0L, + @ProtoId(6) val publicGroup: Boolean = false, + @ProtoId(7) val shieldFlag: Int = 0, + @ProtoId(8) val saveTrafficFlag: Int = 0 ) : ProtoBuf @Serializable internal class PbBindUinMsgReadedConfirmReq( - @SerialId(1) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val bindUin: Long = 0L + @ProtoId(1) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val bindUin: Long = 0L ) : ProtoBuf @Serializable internal class AccostTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val reply: Boolean = false + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val reply: Boolean = false ) : ProtoBuf @Serializable internal class PbDiscussReadedReportResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val confUin: Long = 0L, - @SerialId(4) val memberSeq: Long = 0L, - @SerialId(5) val confSeq: Long = 0L + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val confUin: Long = 0L, + @ProtoId(4) val memberSeq: Long = 0L, + @ProtoId(5) val confSeq: Long = 0L ) : ProtoBuf @Serializable internal class NearByAssistantTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val reply: Boolean = false + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val reply: Boolean = false ) : ProtoBuf @Serializable internal data class MsgSendInfo( - @SerialId(1) val receiver: Int = 0 + @ProtoId(1) val receiver: Int = 0 ) : ProtoBuf @Serializable internal class PubGroupTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val groupUin: Long = 0L + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val groupUin: Long = 0L ) : ProtoBuf @Serializable internal class AddressListTmp( - @SerialId(1) val fromPhone: String = "", - @SerialId(2) val toPhone: String = "", - @SerialId(3) val toUin: Long = 0L, - @SerialId(4) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val fromContactSize: Int = 0 + @ProtoId(1) val fromPhone: String = "", + @ProtoId(2) val toPhone: String = "", + @ProtoId(3) val toUin: Long = 0L, + @ProtoId(4) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val fromContactSize: Int = 0 ) : ProtoBuf @Serializable internal class DisTmp( - @SerialId(1) val disUin: Long = 0L, - @SerialId(2) val toUin: Long = 0L + @ProtoId(1) val disUin: Long = 0L, + @ProtoId(2) val toUin: Long = 0L ) @Serializable internal class PbMsgWithDrawResp( - @SerialId(1) val c2cWithDraw: List? = null, - @SerialId(2) val groupWithDraw: List? = null + @ProtoId(1) val c2cWithDraw: List? = null, + @ProtoId(2) val groupWithDraw: List? = null ) : ProtoBuf @Serializable internal class AuthTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbMsgWithDrawReq( - @SerialId(1) val c2cWithDraw: List? = null, - @SerialId(2) val groupWithDraw: List? = null + @ProtoId(1) val c2cWithDraw: List? = null, + @ProtoId(2) val groupWithDraw: List? = null ) : ProtoBuf internal enum class SyncFlag { @@ -164,81 +164,81 @@ internal class MsgSvc : ProtoBuf { @Serializable internal class PbGetMsgReq( - @SerialId(1) val syncFlag: SyncFlag, - @SerialId(2) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val rambleFlag: Int = 1, - @SerialId(4) val latestRambleNumber: Int = 20, - @SerialId(5) val otherRambleNumber: Int = 3, - @SerialId(6) val onlineSyncFlag: Int = 1, - @SerialId(7) val contextFlag: Int = 0, - @SerialId(8) val whisperSessionId: Int = 0, - @SerialId(9) val msgReqType: Int = 0, - @SerialId(10) val pubaccountCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(11) val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val serverBuf: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val syncFlag: SyncFlag, + @ProtoId(2) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val rambleFlag: Int = 1, + @ProtoId(4) val latestRambleNumber: Int = 20, + @ProtoId(5) val otherRambleNumber: Int = 3, + @ProtoId(6) val onlineSyncFlag: Int = 1, + @ProtoId(7) val contextFlag: Int = 0, + @ProtoId(8) val whisperSessionId: Int = 0, + @ProtoId(9) val msgReqType: Int = 0, + @ProtoId(10) val pubaccountCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val serverBuf: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbGetOneDayRoamMsgReq( - @SerialId(1) val peerUin: Long = 0L, - @SerialId(2) val lastMsgtime: Long = 0L, - @SerialId(3) val random: Long = 0L, - @SerialId(4) val readCnt: Int = 0 + @ProtoId(1) val peerUin: Long = 0L, + @ProtoId(2) val lastMsgtime: Long = 0L, + @ProtoId(3) val random: Long = 0L, + @ProtoId(4) val readCnt: Int = 0 ) : ProtoBuf @Serializable internal class GrpTmp( - @SerialId(1) val groupUin: Long = 0L, - @SerialId(2) val toUin: Long = 0L + @ProtoId(1) val groupUin: Long = 0L, + @ProtoId(2) val toUin: Long = 0L ) : ProtoBuf @Serializable internal class PbGetDiscussMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val discussUin: Long = 0L, - @SerialId(4) val returnEndSeq: Long = 0L, - @SerialId(5) val returnBeginSeq: Long = 0L, - @SerialId(6) val msg: List? = null, - @SerialId(7) val lastGetTime: Long = 0L, - @SerialId(8) val discussInfoSeq: Long = 0L + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val discussUin: Long = 0L, + @ProtoId(4) val returnEndSeq: Long = 0L, + @ProtoId(5) val returnBeginSeq: Long = 0L, + @ProtoId(6) val msg: List? = null, + @ProtoId(7) val lastGetTime: Long = 0L, + @ProtoId(8) val discussInfoSeq: Long = 0L ) : ProtoBuf @Serializable internal class CommTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val c2cType: Int = 0, - @SerialId(3) val svrType: Int = 0, - @SerialId(4) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val reserved: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val c2cType: Int = 0, + @ProtoId(3) val svrType: Int = 0, + @ProtoId(4) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val reserved: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbGroupMsgWithDrawResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val subCmd: Int = 0, - @SerialId(4) val groupType: Int = 0, - @SerialId(5) val groupCode: Long = 0L, - @SerialId(6) val failedMsgList: List? = null, - @SerialId(7) val userdef: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val subCmd: Int = 0, + @ProtoId(4) val groupType: Int = 0, + @ProtoId(5) val groupCode: Long = 0L, + @ProtoId(6) val failedMsgList: List? = null, + @ProtoId(7) val userdef: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf { @Serializable internal class MessageResult( - @SerialId(1) val result: Int = 0, - @SerialId(2) val msgSeq: Int = 0, - @SerialId(3) val msgTime: Int = 0, - @SerialId(4) val msgRandom: Int = 0, - @SerialId(5) val errMsg: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val msgType: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val msgSeq: Int = 0, + @ProtoId(3) val msgTime: Int = 0, + @ProtoId(4) val msgRandom: Int = 0, + @ProtoId(5) val errMsg: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val msgType: Int = 0 ) : ProtoBuf } @Serializable internal class PbC2CReadedReportResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable @@ -246,513 +246,513 @@ internal class MsgSvc : ProtoBuf { @Serializable internal class PbC2CMsgWithDrawReq( - @SerialId(1) val msgInfo: List? = null, - @SerialId(2) val longMessageFlag: Int = 0, - @SerialId(3) val reserved: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val subCmd: Int = 0 + @ProtoId(1) val msgInfo: List? = null, + @ProtoId(2) val longMessageFlag: Int = 0, + @ProtoId(3) val reserved: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val subCmd: Int = 0 ) : ProtoBuf { @Serializable internal class MsgInfo( - @SerialId(1) val fromUin: Long = 0L, - @SerialId(2) val toUin: Long = 0L, - @SerialId(3) val msgSeq: Int = 0, - @SerialId(4) val msgUid: Long = 0L, - @SerialId(5) val msgTime: Long = 0L, - @SerialId(6) val msgRandom: Int = 0, - @SerialId(7) val pkgNum: Int = 0, - @SerialId(8) val pkgIndex: Int = 0, - @SerialId(9) val divSeq: Int = 0, - @SerialId(10) val msgType: Int = 0, - @SerialId(20) val routingHead: RoutingHead? = null + @ProtoId(1) val fromUin: Long = 0L, + @ProtoId(2) val toUin: Long = 0L, + @ProtoId(3) val msgSeq: Int = 0, + @ProtoId(4) val msgUid: Long = 0L, + @ProtoId(5) val msgTime: Long = 0L, + @ProtoId(6) val msgRandom: Int = 0, + @ProtoId(7) val pkgNum: Int = 0, + @ProtoId(8) val pkgIndex: Int = 0, + @ProtoId(9) val divSeq: Int = 0, + @ProtoId(10) val msgType: Int = 0, + @ProtoId(20) val routingHead: RoutingHead? = null ) } @Serializable internal class PbDelRoamMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "" + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "" ) : ProtoBuf @Serializable internal class Dis( - @SerialId(1) val disUin: Long = 0L + @ProtoId(1) val disUin: Long = 0L ) : ProtoBuf @Serializable internal class TransSvrInfo( - @SerialId(1) val subType: Int = 0, - @SerialId(2) val int32RetCode: Int = 0, - @SerialId(3) val errMsg: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val transInfo: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val subType: Int = 0, + @ProtoId(2) val int32RetCode: Int = 0, + @ProtoId(3) val errMsg: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val transInfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbPullGroupMsgSeqResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val groupInfoResp: List? = null + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val groupInfoResp: List? = null ) : ProtoBuf { @Serializable internal class GroupInfoResp( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val memberSeq: Long = 0L, - @SerialId(3) val groupSeq: Long = 0L + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val memberSeq: Long = 0L, + @ProtoId(3) val groupSeq: Long = 0L ) } @Serializable internal class PbSendMsgReq( - @SerialId(1) val routingHead: RoutingHead? = null, - @SerialId(2) val contentHead: MsgComm.ContentHead? = null, - @SerialId(3) val msgBody: ImMsgBody.MsgBody = ImMsgBody.MsgBody(), - @SerialId(4) val msgSeq: Int = 0, - @SerialId(5) val msgRand: Int = 0, - @SerialId(6) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val appShare: MsgComm.AppShareInfo? = null, - @SerialId(8) val msgVia: Int = 0, - @SerialId(9) val dataStatist: Int = 0, - @SerialId(10) val multiMsgAssist: MultiMsgAssist? = null, - @SerialId(11) val inputNotifyInfo: PbInputNotifyInfo? = null, - @SerialId(12) val msgCtrl: MsgCtrl.MsgCtrl? = null, - @SerialId(13) val receiptReq: ImReceipt.ReceiptReq? = null, - @SerialId(14) val multiSendSeq: Int = 0 + @ProtoId(1) val routingHead: RoutingHead? = null, + @ProtoId(2) val contentHead: MsgComm.ContentHead? = null, + @ProtoId(3) val msgBody: ImMsgBody.MsgBody = ImMsgBody.MsgBody(), + @ProtoId(4) val msgSeq: Int = 0, + @ProtoId(5) val msgRand: Int = 0, + @ProtoId(6) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val appShare: MsgComm.AppShareInfo? = null, + @ProtoId(8) val msgVia: Int = 0, + @ProtoId(9) val dataStatist: Int = 0, + @ProtoId(10) val multiMsgAssist: MultiMsgAssist? = null, + @ProtoId(11) val inputNotifyInfo: PbInputNotifyInfo? = null, + @ProtoId(12) val msgCtrl: MsgCtrl.MsgCtrl? = null, + @ProtoId(13) val receiptReq: ImReceipt.ReceiptReq? = null, + @ProtoId(14) val multiSendSeq: Int = 0 ) : ProtoBuf @Serializable internal class TransMsg( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val c2cCmd: Int = 0 + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val c2cCmd: Int = 0 ) : ProtoBuf @Serializable internal class PbDeleteMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "" + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "" ) : ProtoBuf @Serializable internal class PbSearchRoamMsgInCloudResp( - @SerialId(1) val msg: List? = null, - @SerialId(2) val serializeRspbody: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val msg: List? = null, + @ProtoId(2) val serializeRspbody: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbInputNotifyInfo( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val ime: Int = 0, - @SerialId(3) val notifyFlag: Int = 0, - @SerialId(4) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val iosPushWording: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val ime: Int = 0, + @ProtoId(3) val notifyFlag: Int = 0, + @ProtoId(4) val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val iosPushWording: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbUnReadMsgSeqResp( - @SerialId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumResp? = null, - @SerialId(2) val binduinUnreadInfo: List? = null, - @SerialId(3) val groupUnreadInfo: PbPullGroupMsgSeqResp? = null, - @SerialId(4) val discussUnreadInfo: PbPullDiscussMsgSeqResp? = null, - @SerialId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumResp? = null + @ProtoId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumResp? = null, + @ProtoId(2) val binduinUnreadInfo: List? = null, + @ProtoId(3) val groupUnreadInfo: PbPullGroupMsgSeqResp? = null, + @ProtoId(4) val discussUnreadInfo: PbPullDiscussMsgSeqResp? = null, + @ProtoId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumResp? = null ) : ProtoBuf @Serializable internal class PbDeleteMsgReq( - @SerialId(1) val msgItems: List? = null + @ProtoId(1) val msgItems: List? = null ) : ProtoBuf { @Serializable internal class MsgItem( - @SerialId(1) val fromUin: Long = 0L, - @SerialId(2) val toUin: Long = 0L, - @SerialId(3) val msgType: Int = 0, - @SerialId(4) val msgSeq: Int = 0, - @SerialId(5) val msgUid: Long = 0L, - @SerialId(7) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val fromUin: Long = 0L, + @ProtoId(2) val toUin: Long = 0L, + @ProtoId(3) val msgType: Int = 0, + @ProtoId(4) val msgSeq: Int = 0, + @ProtoId(5) val msgUid: Long = 0L, + @ProtoId(7) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class MultiMsgAssist( - @SerialId(1) val repeatedRouting: List? = null, - @SerialId(2) val msgUse: Int /* enum */ = 1, - @SerialId(3) val tempId: Long = 0L, - @SerialId(4) val vedioLen: Long = 0L, - @SerialId(5) val redbagId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val redbagAmount: Long = 0L, - @SerialId(7) val hasReadbag: Int = 0, - @SerialId(8) val hasVedio: Int = 0 + @ProtoId(1) val repeatedRouting: List? = null, + @ProtoId(2) val msgUse: Int /* enum */ = 1, + @ProtoId(3) val tempId: Long = 0L, + @ProtoId(4) val vedioLen: Long = 0L, + @ProtoId(5) val redbagId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val redbagAmount: Long = 0L, + @ProtoId(7) val hasReadbag: Int = 0, + @ProtoId(8) val hasVedio: Int = 0 ) : ProtoBuf @Serializable internal class PbMsgReadedReportReq( - @SerialId(1) val grpReadReport: List? = null, - @SerialId(2) val disReadReport: List? = null, - @SerialId(3) val c2cReadReport: PbC2CReadedReportReq? = null, - @SerialId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmReq? = null + @ProtoId(1) val grpReadReport: List? = null, + @ProtoId(2) val disReadReport: List? = null, + @ProtoId(3) val c2cReadReport: PbC2CReadedReportReq? = null, + @ProtoId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmReq? = null ) : ProtoBuf @Serializable internal class PbGetOneDayRoamMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val peerUin: Long = 0L, - @SerialId(4) val lastMsgtime: Long = 0L, - @SerialId(5) val random: Long = 0L, - @SerialId(6) val msg: List? = null, - @SerialId(7) val iscomplete: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val peerUin: Long = 0L, + @ProtoId(4) val lastMsgtime: Long = 0L, + @ProtoId(5) val random: Long = 0L, + @ProtoId(6) val msg: List? = null, + @ProtoId(7) val iscomplete: Int = 0 ) : ProtoBuf @Serializable internal class PbBindUinGetMsgReq( - @SerialId(1) val bindUin: Long = 0L, - @SerialId(2) val bindUinSig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val syncFlag: Int /* enum */ = 0, - @SerialId(4) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val bindUin: Long = 0L, + @ProtoId(2) val bindUinSig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val syncFlag: Int /* enum */ = 0, + @ProtoId(4) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class NearByDatingTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val reply: Boolean = false + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val reply: Boolean = false ) : ProtoBuf @Serializable internal class BsnsTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RoutingHead( - @SerialId(1) val c2c: C2C? = null, - @SerialId(2) val grp: Grp? = null, - @SerialId(3) val grpTmp: GrpTmp? = null, - @SerialId(4) val dis: Dis? = null, - @SerialId(5) val disTmp: DisTmp? = null, - @SerialId(6) val wpaTmp: WPATmp? = null, - @SerialId(7) val secretFile: SecretFileHead? = null, - @SerialId(8) val publicPlat: PublicPlat? = null, - @SerialId(9) val transMsg: TransMsg? = null, - @SerialId(10) val addressList: AddressListTmp? = null, - @SerialId(11) val richStatusTmp: RichStatusTmp? = null, - @SerialId(12) val transCmd: TransCmd? = null, - @SerialId(13) val accostTmp: AccostTmp? = null, - @SerialId(14) val pubGroupTmp: PubGroupTmp? = null, - @SerialId(15) val trans0x211: Trans0x211? = null, - @SerialId(16) val businessWpaTmp: BusinessWPATmp? = null, - @SerialId(17) val authTmp: AuthTmp? = null, - @SerialId(18) val bsnsTmp: BsnsTmp? = null, - @SerialId(19) val qqQuerybusinessTmp: QQQueryBusinessTmp? = null, - @SerialId(20) val nearbyDatingTmp: NearByDatingTmp? = null, - @SerialId(21) val nearbyAssistantTmp: NearByAssistantTmp? = null, - @SerialId(22) val commTmp: CommTmp? = null + @ProtoId(1) val c2c: C2C? = null, + @ProtoId(2) val grp: Grp? = null, + @ProtoId(3) val grpTmp: GrpTmp? = null, + @ProtoId(4) val dis: Dis? = null, + @ProtoId(5) val disTmp: DisTmp? = null, + @ProtoId(6) val wpaTmp: WPATmp? = null, + @ProtoId(7) val secretFile: SecretFileHead? = null, + @ProtoId(8) val publicPlat: PublicPlat? = null, + @ProtoId(9) val transMsg: TransMsg? = null, + @ProtoId(10) val addressList: AddressListTmp? = null, + @ProtoId(11) val richStatusTmp: RichStatusTmp? = null, + @ProtoId(12) val transCmd: TransCmd? = null, + @ProtoId(13) val accostTmp: AccostTmp? = null, + @ProtoId(14) val pubGroupTmp: PubGroupTmp? = null, + @ProtoId(15) val trans0x211: Trans0x211? = null, + @ProtoId(16) val businessWpaTmp: BusinessWPATmp? = null, + @ProtoId(17) val authTmp: AuthTmp? = null, + @ProtoId(18) val bsnsTmp: BsnsTmp? = null, + @ProtoId(19) val qqQuerybusinessTmp: QQQueryBusinessTmp? = null, + @ProtoId(20) val nearbyDatingTmp: NearByDatingTmp? = null, + @ProtoId(21) val nearbyAssistantTmp: NearByAssistantTmp? = null, + @ProtoId(22) val commTmp: CommTmp? = null ) : ProtoBuf @Serializable internal class TransResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val respTag: Int = 0, - @SerialId(4) val respBuff: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val respTag: Int = 0, + @ProtoId(4) val respBuff: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal data class PbSendMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val sendTime: Int = 0, - @SerialId(4) val svrbusyWaitTime: Int = 0, - @SerialId(5) val msgSendInfo: MsgSendInfo? = null, - @SerialId(6) val errtype: Int = 0, - @SerialId(7) val transSvrInfo: TransSvrInfo? = null, - @SerialId(8) val receiptResp: ImReceipt.ReceiptResp? = null, - @SerialId(9) val textAnalysisResult: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val sendTime: Int = 0, + @ProtoId(4) val svrbusyWaitTime: Int = 0, + @ProtoId(5) val msgSendInfo: MsgSendInfo? = null, + @ProtoId(6) val errtype: Int = 0, + @ProtoId(7) val transSvrInfo: TransSvrInfo? = null, + @ProtoId(8) val receiptResp: ImReceipt.ReceiptResp? = null, + @ProtoId(9) val textAnalysisResult: Int = 0 ) : ProtoBuf, Packet @Serializable internal class PbBindUinUnReadMsgNumResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val bindUin: Long = 0L, - @SerialId(4) val msgNum: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val bindUin: Long = 0L, + @ProtoId(4) val msgNum: Int = 0 ) : ProtoBuf @Serializable internal class PbGetDiscussMsgReq( - @SerialId(1) val discussUin: Long = 0L, - @SerialId(2) val endSeq: Long = 0L, - @SerialId(3) val beginSeq: Long = 0L, - @SerialId(4) val lastGetTime: Long = 0L, - @SerialId(5) val discussInfoSeq: Long = 0L, - @SerialId(6) val filter: Int /* enum */ = 0, - @SerialId(7) val memberSeq: Long = 0L + @ProtoId(1) val discussUin: Long = 0L, + @ProtoId(2) val endSeq: Long = 0L, + @ProtoId(3) val beginSeq: Long = 0L, + @ProtoId(4) val lastGetTime: Long = 0L, + @ProtoId(5) val discussInfoSeq: Long = 0L, + @ProtoId(6) val filter: Int /* enum */ = 0, + @ProtoId(7) val memberSeq: Long = 0L ) : ProtoBuf @Serializable internal class PbC2CMsgWithDrawResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val msgStatus: List? = null, - @SerialId(4) val subCmd: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val msgStatus: List? = null, + @ProtoId(4) val subCmd: Int = 0 ) : ProtoBuf { @Serializable internal class MsgStatus( - @SerialId(1) val msgInfo: PbC2CMsgWithDrawReq.MsgInfo? = null, - @SerialId(2) val status: Int = 0 + @ProtoId(1) val msgInfo: PbC2CMsgWithDrawReq.MsgInfo? = null, + @ProtoId(2) val status: Int = 0 ) : ProtoBuf } @Serializable internal class SecretFileHead( - @SerialId(1) val secretFileMsg: SubMsgType0xc1.MsgBody? = null, - @SerialId(2) val secretFileStatus: SubMsgType0x1a.MsgBody? = null + @ProtoId(1) val secretFileMsg: SubMsgType0xc1.MsgBody? = null, + @ProtoId(2) val secretFileStatus: SubMsgType0x1a.MsgBody? = null ) @Serializable internal class PbGetRoamMsgReq( - @SerialId(1) val peerUin: Long = 0L, - @SerialId(2) val lastMsgtime: Long = 0L, - @SerialId(3) val random: Long = 0L, - @SerialId(4) val readCnt: Int = 0, - @SerialId(5) val checkPwd: Int = 0, - @SerialId(6) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val pwd: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val subcmd: Int = 0, - @SerialId(9) val beginMsgtime: Long = 0L, - @SerialId(10) val reqType: Int = 0 + @ProtoId(1) val peerUin: Long = 0L, + @ProtoId(2) val lastMsgtime: Long = 0L, + @ProtoId(3) val random: Long = 0L, + @ProtoId(4) val readCnt: Int = 0, + @ProtoId(5) val checkPwd: Int = 0, + @ProtoId(6) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val pwd: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val subcmd: Int = 0, + @ProtoId(9) val beginMsgtime: Long = 0L, + @ProtoId(10) val reqType: Int = 0 ) : ProtoBuf @Serializable internal class TransCmd( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val msgType: Int = 0 + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val msgType: Int = 0 ) : ProtoBuf @Serializable internal class PbMsgReadedReportResp( - @SerialId(1) val grpReadReport: List? = null, - @SerialId(2) val disReadReport: List? = null, - @SerialId(3) val c2cReadReport: PbC2CReadedReportResp? = null, - @SerialId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmResp? = null + @ProtoId(1) val grpReadReport: List? = null, + @ProtoId(2) val disReadReport: List? = null, + @ProtoId(3) val c2cReadReport: PbC2CReadedReportResp? = null, + @ProtoId(4) val bindUinReadReport: PbBindUinMsgReadedConfirmResp? = null ) : ProtoBuf @Serializable internal class PbThirdQQUnReadMsgNumResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val thirdqqRespInfo: List? = null, - @SerialId(4) val interval: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val thirdqqRespInfo: List? = null, + @ProtoId(4) val interval: Int = 0 ) : ProtoBuf { @Serializable internal class ThirdQQRespInfo( - @SerialId(1) val thirdUin: Long = 0L, - @SerialId(2) val thirdUinCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val msgNum: Int = 0, - @SerialId(4) val msgFlag: Int = 0, - @SerialId(5) val redbagTime: Int = 0, - @SerialId(6) val status: Int = 0, - @SerialId(7) val lastMsgTime: Int = 0 + @ProtoId(1) val thirdUin: Long = 0L, + @ProtoId(2) val thirdUinCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val msgNum: Int = 0, + @ProtoId(4) val msgFlag: Int = 0, + @ProtoId(5) val redbagTime: Int = 0, + @ProtoId(6) val status: Int = 0, + @ProtoId(7) val lastMsgTime: Int = 0 ) : ProtoBuf } @Serializable internal class RichStatusTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class QQQueryBusinessTmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbDelRoamMsgReq( - @SerialId(1) val c2cMsg: C2CMsg? = null, - @SerialId(2) val grpMsg: GrpMsg? = null, - @SerialId(3) val disMsg: DisMsg? = null + @ProtoId(1) val c2cMsg: C2CMsg? = null, + @ProtoId(2) val grpMsg: GrpMsg? = null, + @ProtoId(3) val disMsg: DisMsg? = null ) : ProtoBuf { @Serializable internal class GrpMsg( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val msgSeq: Long = 0L + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val msgSeq: Long = 0L ) : ProtoBuf @Serializable internal class C2CMsg( - @SerialId(1) val fromUin: Long = 0L, - @SerialId(2) val peerUin: Long = 0L, - @SerialId(3) val msgTime: Int = 0, - @SerialId(4) val msgRandom: Int = 0, - @SerialId(5) val msgSeq: Int = 0 + @ProtoId(1) val fromUin: Long = 0L, + @ProtoId(2) val peerUin: Long = 0L, + @ProtoId(3) val msgTime: Int = 0, + @ProtoId(4) val msgRandom: Int = 0, + @ProtoId(5) val msgSeq: Int = 0 ) : ProtoBuf @Serializable internal class DisMsg( - @SerialId(1) val discussUin: Long = 0L, - @SerialId(2) val msgSeq: Long = 0L + @ProtoId(1) val discussUin: Long = 0L, + @ProtoId(2) val msgSeq: Long = 0L ) : ProtoBuf } @Serializable internal class PbUnReadMsgSeqReq( - @SerialId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumReq? = null, - @SerialId(2) val binduinUnreadInfo: List? = null, - @SerialId(3) val groupUnreadInfo: PbPullGroupMsgSeqReq? = null, - @SerialId(4) val discussUnreadInfo: PbPullDiscussMsgSeqReq? = null, - @SerialId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumReq? = null + @ProtoId(1) val c2cUnreadInfo: PbC2CUnReadMsgNumReq? = null, + @ProtoId(2) val binduinUnreadInfo: List? = null, + @ProtoId(3) val groupUnreadInfo: PbPullGroupMsgSeqReq? = null, + @ProtoId(4) val discussUnreadInfo: PbPullDiscussMsgSeqReq? = null, + @ProtoId(5) val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumReq? = null ) : ProtoBuf @Serializable internal class PbPullDiscussMsgSeqResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val discussInfoResp: List? = null + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val discussInfoResp: List? = null ) : ProtoBuf { @Serializable internal class DiscussInfoResp( - @SerialId(1) val confUin: Long = 0L, - @SerialId(2) val memberSeq: Long = 0L, - @SerialId(3) val confSeq: Long = 0L + @ProtoId(1) val confUin: Long = 0L, + @ProtoId(2) val memberSeq: Long = 0L, + @ProtoId(3) val confSeq: Long = 0L ) : ProtoBuf } @Serializable internal class PbPullDiscussMsgSeqReq( - @SerialId(1) val discussInfoReq: List? = null + @ProtoId(1) val discussInfoReq: List? = null ) : ProtoBuf { @Serializable internal class DiscussInfoReq( - @SerialId(1) val confUin: Long = 0L, - @SerialId(2) val lastSeq: Long = 0L + @ProtoId(1) val confUin: Long = 0L, + @ProtoId(2) val lastSeq: Long = 0L ) : ProtoBuf } @Serializable internal class WPATmp( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PublicPlat( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbBindUinMsgReadedConfirmResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val bindUin: Long = 0L + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val bindUin: Long = 0L ) : ProtoBuf @Serializable internal class PbGetRoamMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val peerUin: Long = 0L, - @SerialId(4) val lastMsgtime: Long = 0L, - @SerialId(5) val random: Long = 0L, - @SerialId(6) val msg: List? = null, - @SerialId(7) val sig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val peerUin: Long = 0L, + @ProtoId(4) val lastMsgtime: Long = 0L, + @ProtoId(5) val random: Long = 0L, + @ProtoId(6) val msg: List? = null, + @ProtoId(7) val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbDiscussReadedReportReq( - @SerialId(1) val confUin: Long = 0L, - @SerialId(2) val lastReadSeq: Long = 0L + @ProtoId(1) val confUin: Long = 0L, + @ProtoId(2) val lastReadSeq: Long = 0L ) : ProtoBuf @Serializable internal class PbC2CReadedReportReq( - @SerialId(1) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val pairInfo: List? = null + @ProtoId(1) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val pairInfo: List? = null ) : ProtoBuf { @Serializable internal class UinPairReadInfo( - @SerialId(1) val peerUin: Long = 0L, - @SerialId(2) val lastReadTime: Int = 0, - @SerialId(3) val crmSig: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val peerUin: Long = 0L, + @ProtoId(2) val lastReadTime: Int = 0, + @ProtoId(3) val crmSig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Trans0x211( - @SerialId(1) val toUin: Long = 0L, - @SerialId(2) val ccCmd: Int = 0, - @SerialId(3) val instCtrl: ImMsgHead.InstCtrl? = null, - @SerialId(4) val sig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val c2cType: Int = 0, - @SerialId(6) val serviceType: Int = 0 + @ProtoId(1) val toUin: Long = 0L, + @ProtoId(2) val ccCmd: Int = 0, + @ProtoId(3) val instCtrl: ImMsgHead.InstCtrl? = null, + @ProtoId(4) val sig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val c2cType: Int = 0, + @ProtoId(6) val serviceType: Int = 0 ) : ProtoBuf @Serializable internal class PbSearchRoamMsgInCloudReq( - @SerialId(1) val serializeReqbody: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val serializeReqbody: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbBindUinUnReadMsgNumReq( - @SerialId(1) val bindUin: Long = 0L, - @SerialId(2) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val bindUin: Long = 0L, + @ProtoId(2) val syncCookie: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbC2CUnReadMsgNumResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val msgNum: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val msgNum: Int = 0 ) : ProtoBuf @Serializable internal class PbPullGroupMsgSeqReq( - @SerialId(1) val groupInfoReq: List? = null + @ProtoId(1) val groupInfoReq: List? = null ) : ProtoBuf { @Serializable internal class GroupInfoReq( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val lastSeq: Long = 0L + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val lastSeq: Long = 0L ) : ProtoBuf } @Serializable internal class TransReq( - @SerialId(1) val command: Int = 0, - @SerialId(2) val reqTag: Int = 0, - @SerialId(3) val reqBuff: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val command: Int = 0, + @ProtoId(2) val reqTag: Int = 0, + @ProtoId(3) val reqBuff: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PbGroupReadedReportResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val groupCode: Long = 0L, - @SerialId(4) val memberSeq: Long = 0L, - @SerialId(5) val groupMsgSeq: Long = 0L + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val groupCode: Long = 0L, + @ProtoId(4) val memberSeq: Long = 0L, + @ProtoId(5) val groupMsgSeq: Long = 0L ) : ProtoBuf @Serializable internal class PbGetGroupMsgResp( - @SerialId(1) val result: Int = 0, - @SerialId(2) val errmsg: String = "", - @SerialId(3) val groupCode: Long = 0L, - @SerialId(4) val returnBeginSeq: Long = 0L, - @SerialId(5) val returnEndSeq: Long = 0L, - @SerialId(6) val msg: List? = null + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val errmsg: String = "", + @ProtoId(3) val groupCode: Long = 0L, + @ProtoId(4) val returnBeginSeq: Long = 0L, + @ProtoId(5) val returnEndSeq: Long = 0L, + @ProtoId(6) val msg: List? = null ) : ProtoBuf @Serializable internal class PbThirdQQUnReadMsgNumReq( - @SerialId(1) val thirdqqReqInfo: List? = null, - @SerialId(2) val source: Int = 0 + @ProtoId(1) val thirdqqReqInfo: List? = null, + @ProtoId(2) val source: Int = 0 ) : ProtoBuf { @Serializable internal class ThirdQQReqInfo( - @SerialId(1) val thirdUin: Long = 0L, - @SerialId(2) val thirdUinSig: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val thirdUinCookie: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val thirdUin: Long = 0L, + @ProtoId(2) val thirdUinSig: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val thirdUinCookie: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } } @@ -761,20 +761,20 @@ internal class MsgSvc : ProtoBuf { internal class MsgCtrl { @Serializable internal class MsgCtrl( - @SerialId(1) val msgFlag: Int = 0, - @SerialId(2) val resvResvInfo: ResvResvInfo? = null + @ProtoId(1) val msgFlag: Int = 0, + @ProtoId(2) val resvResvInfo: ResvResvInfo? = null ) : ProtoBuf @Serializable internal class ResvResvInfo( - @SerialId(1) val flag: Int = 0, - @SerialId(2) val reserv1: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val reserv2: Long = 0L, - @SerialId(4) val reserv3: Long = 0L, - @SerialId(5) val createTime: Int = 0, - @SerialId(6) val picHeight: Int = 0, - @SerialId(7) val picWidth: Int = 0, - @SerialId(8) val resvFlag: Int = 0 + @ProtoId(1) val flag: Int = 0, + @ProtoId(2) val reserv1: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val reserv2: Long = 0L, + @ProtoId(4) val reserv3: Long = 0L, + @ProtoId(5) val createTime: Int = 0, + @ProtoId(6) val picHeight: Int = 0, + @ProtoId(7) val picWidth: Int = 0, + @ProtoId(8) val resvFlag: Int = 0 ) : ProtoBuf } @@ -782,35 +782,35 @@ internal class MsgCtrl { internal class SubMsgType0xc1 { @Serializable internal class NotOnlineImage( - @SerialId(1) val filePath: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val fileLen: Int = 0, - @SerialId(3) val downloadPath: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val imgType: Int = 0, - @SerialId(6) val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val picMd5: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val picHeight: Int = 0, - @SerialId(9) val picWidth: Int = 0, - @SerialId(10) val resId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(11) val flag: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val downloadUrl: String = "", - @SerialId(13) val original: Int = 0 + @ProtoId(1) val filePath: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val fileLen: Int = 0, + @ProtoId(3) val downloadPath: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val imgType: Int = 0, + @ProtoId(6) val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val picMd5: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val picHeight: Int = 0, + @ProtoId(9) val picWidth: Int = 0, + @ProtoId(10) val resId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val flag: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val downloadUrl: String = "", + @ProtoId(13) val original: Int = 0 ) : ProtoBuf @Serializable internal class MsgBody( - @SerialId(1) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val fromUin: Long = 0L, - @SerialId(3) val toUin: Long = 0L, - @SerialId(4) val status: Int = 0, - @SerialId(5) val ttl: Int = 0, - @SerialId(6) val type: Int = 0, - @SerialId(7) val encryptPreheadLength: Int = 0, - @SerialId(8) val encryptType: Int = 0, - @SerialId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val readTimes: Int = 0, - @SerialId(11) val leftTime: Int = 0, - @SerialId(12) val notOnlineImage: NotOnlineImage? = null + @ProtoId(1) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val fromUin: Long = 0L, + @ProtoId(3) val toUin: Long = 0L, + @ProtoId(4) val status: Int = 0, + @ProtoId(5) val ttl: Int = 0, + @ProtoId(6) val type: Int = 0, + @ProtoId(7) val encryptPreheadLength: Int = 0, + @ProtoId(8) val encryptType: Int = 0, + @ProtoId(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val readTimes: Int = 0, + @ProtoId(11) val leftTime: Int = 0, + @ProtoId(12) val notOnlineImage: NotOnlineImage? = null ) : ProtoBuf } @@ -818,15 +818,15 @@ internal class SubMsgType0xc1 { internal class SubMsgType0x1a { @Serializable internal class MsgBody( - @SerialId(1) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val fromUin_int32: Int = 0, - @SerialId(3) val toUin_int32: Int = 0, - @SerialId(4) val status: Int = 0, - @SerialId(5) val ttl: Int = 0, - @SerialId(6) val ingDesc: String = "", - @SerialId(7) val type: Int = 0, - @SerialId(8) val captureTimes: Int = 0, - @SerialId(9) val fromUin: Long = 0L, - @SerialId(10) val toUin: Long = 0L + @ProtoId(1) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val fromUin_int32: Int = 0, + @ProtoId(3) val toUin_int32: Int = 0, + @ProtoId(4) val status: Int = 0, + @ProtoId(5) val ttl: Int = 0, + @ProtoId(6) val ingDesc: String = "", + @ProtoId(7) val type: Int = 0, + @ProtoId(8) val captureTimes: Int = 0, + @ProtoId(9) val fromUin: Long = 0L, + @ProtoId(10) val toUin: Long = 0L ) : ProtoBuf } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt index e12633005..53459082c 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/data/proto/OIDB.kt @@ -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,31 +18,31 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.EMPTY_BYTE_ARRAY class Oidb0x8a0 : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val optUint64GroupCode: Long = 0L, - @SerialId(2) val msgKickResult: List? = null + @ProtoId(1) val optUint64GroupCode: Long = 0L, + @ProtoId(2) val msgKickResult: List? = null ) : ProtoBuf @Serializable class KickResult( - @SerialId(1) val optUint32Result: Int = 0, - @SerialId(2) val optUint64MemberUin: Long = 0L + @ProtoId(1) val optUint32Result: Int = 0, + @ProtoId(2) val optUint64MemberUin: Long = 0L ) : ProtoBuf @Serializable class KickMemberInfo( - @SerialId(1) val optUint32Operate: Int = 0, - @SerialId(2) val optUint64MemberUin: Long = 0L, - @SerialId(3) val optUint32Flag: Int = 0, - @SerialId(4) val optBytesMsg: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val optUint32Operate: Int = 0, + @ProtoId(2) val optUint64MemberUin: Long = 0L, + @ProtoId(3) val optUint32Flag: Int = 0, + @ProtoId(4) val optBytesMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val optUint64GroupCode: Long = 0L, - @SerialId(2) val msgKickList: List? = null, - @SerialId(3) val kickList: List? = null, - @SerialId(4) val kickFlag: Int = 0, - @SerialId(5) val kickMsg: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val optUint64GroupCode: Long = 0L, + @ProtoId(2) val msgKickList: List? = null, + @ProtoId(3) val kickList: List? = null, + @ProtoId(4) val kickFlag: Int = 0, + @ProtoId(5) val kickMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @@ -51,71 +51,71 @@ class Oidb0x8a0 : ProtoBuf { class Oidb0x8fc : ProtoBuf { @Serializable class CardNameElem( - @SerialId(1) val enumCardType: Int /* enum */ = 1, - @SerialId(2) val value: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val enumCardType: Int /* enum */ = 1, + @ProtoId(2) val value: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class CommCardNameBuf( - @SerialId(1) val richCardName: List? = null + @ProtoId(1) val richCardName: List? = null ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val showFlag: Int = 0, - @SerialId(3) val memLevelInfo: List? = null, - @SerialId(4) val levelName: List? = null, - @SerialId(5) val updateTime: Int = 0, - @SerialId(6) val officeMode: Int = 0, - @SerialId(7) val groupOpenAppid: Int = 0, - @SerialId(8) val msgClientInfo: ClientInfo? = null, - @SerialId(9) val authKey: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val showFlag: Int = 0, + @ProtoId(3) val memLevelInfo: List? = null, + @ProtoId(4) val levelName: List? = null, + @ProtoId(5) val updateTime: Int = 0, + @ProtoId(6) val officeMode: Int = 0, + @ProtoId(7) val groupOpenAppid: Int = 0, + @ProtoId(8) val msgClientInfo: ClientInfo? = null, + @ProtoId(9) val authKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class MemberInfo( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val point: Int = 0, - @SerialId(3) val activeDay: Int = 0, - @SerialId(4) val level: Int = 0, - @SerialId(5) val specialTitle: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val specialTitleExpireTime: Int = 0, - @SerialId(7) val uinName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val memberCardName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val phone: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val email: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(11) val remark: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(12) val gender: Int = 0, - @SerialId(13) val job: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(14) val tribeLevel: Int = 0, - @SerialId(15) val tribePoint: Int = 0, - @SerialId(16) val richCardName: List? = null, - @SerialId(17) val commRichCardName: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val point: Int = 0, + @ProtoId(3) val activeDay: Int = 0, + @ProtoId(4) val level: Int = 0, + @ProtoId(5) val specialTitle: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val specialTitleExpireTime: Int = 0, + @ProtoId(7) val uinName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val memberCardName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(9) val phone: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val email: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(11) val remark: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(12) val gender: Int = 0, + @ProtoId(13) val job: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(14) val tribeLevel: Int = 0, + @ProtoId(15) val tribePoint: Int = 0, + @ProtoId(16) val richCardName: List? = null, + @ProtoId(17) val commRichCardName: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class RichCardNameElem( - @SerialId(1) val ctrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val text: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val ctrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val text: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val errInfo: String = "" + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val errInfo: String = "" ) : ProtoBuf @Serializable class ClientInfo( - @SerialId(1) val implat: Int = 0, - @SerialId(2) val ingClientver: String = "" + @ProtoId(1) val implat: Int = 0, + @ProtoId(2) val ingClientver: String = "" ) : ProtoBuf @Serializable class LevelName( - @SerialId(1) val level: Int = 0, - @SerialId(2) val name: String = "" + @ProtoId(1) val level: Int = 0, + @ProtoId(2) val name: String = "" ) : ProtoBuf } @@ -123,181 +123,181 @@ class Oidb0x8fc : ProtoBuf { class Oidb0x88d : ProtoBuf { @Serializable class GroupExInfoOnly( - @SerialId(1) val tribeId: Int = 0, - @SerialId(2) val moneyForAddGroup: Int = 0 + @ProtoId(1) val tribeId: Int = 0, + @ProtoId(2) val moneyForAddGroup: Int = 0 ) : ProtoBuf @Serializable class ReqGroupInfo( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val stgroupinfo: GroupInfo? = null, - @SerialId(3) val lastGetGroupNameTime: Int = 0 + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val stgroupinfo: GroupInfo? = null, + @ProtoId(3) val lastGetGroupNameTime: Int = 0 ) : ProtoBuf @Serializable class RspGroupInfo( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val result: Int = 0, - @SerialId(3) val stgroupinfo: GroupInfo? = null + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val result: Int = 0, + @ProtoId(3) val stgroupinfo: GroupInfo? = null ) : ProtoBuf @Serializable class GroupGeoInfo( - @SerialId(1) val owneruin: Long = 0L, - @SerialId(2) val settime: Int = 0, - @SerialId(3) val cityid: Int = 0, - @SerialId(4) val int64Longitude: Long = 0L, - @SerialId(5) val int64Latitude: Long = 0L, - @SerialId(6) val geocontent: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val poiId: Long = 0L + @ProtoId(1) val owneruin: Long = 0L, + @ProtoId(2) val settime: Int = 0, + @ProtoId(3) val cityid: Int = 0, + @ProtoId(4) val int64Longitude: Long = 0L, + @ProtoId(5) val int64Latitude: Long = 0L, + @ProtoId(6) val geocontent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(7) val poiId: Long = 0L ) : ProtoBuf @Serializable class TagRecord( - @SerialId(1) val fromUin: Long = 0L, - @SerialId(2) val groupCode: Long = 0L, - @SerialId(3) val tagId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val setTime: Long = 0L, - @SerialId(5) val goodNum: Int = 0, - @SerialId(6) val badNum: Int = 0, - @SerialId(7) val tagLen: Int = 0, - @SerialId(8) val tagValue: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val fromUin: Long = 0L, + @ProtoId(2) val groupCode: Long = 0L, + @ProtoId(3) val tagId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val setTime: Long = 0L, + @ProtoId(5) val goodNum: Int = 0, + @ProtoId(6) val badNum: Int = 0, + @ProtoId(7) val tagLen: Int = 0, + @ProtoId(8) val tagValue: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class GroupInfo( - @SerialId(1) val groupOwner: Long? = null, - @SerialId(2) val groupCreateTime: Int? = null, - @SerialId(3) val groupFlag: Int? = null, - @SerialId(4) val groupFlagExt: Int? = null, - @SerialId(5) val groupMemberMaxNum: Int? = null, - @SerialId(6) val groupMemberNum: Int? = null, - @SerialId(7) val groupOption: Int? = null, - @SerialId(8) val groupClassExt: Int? = null, - @SerialId(9) val groupSpecialClass: Int? = null, - @SerialId(10) val groupLevel: Int? = null, - @SerialId(11) val groupFace: Int? = null, - @SerialId(12) val groupDefaultPage: Int? = null, - @SerialId(13) val groupInfoSeq: Int? = null, - @SerialId(14) val groupRoamingTime: Int? = null, - @SerialId(15) var groupName: String? = null, - @SerialId(16) var groupMemo: String? = null, - @SerialId(17) val ingGroupFingerMemo: String? = null, - @SerialId(18) val ingGroupClassText: String? = null, - @SerialId(19) val groupAllianceCode: List? = null, - @SerialId(20) val groupExtraAdmNum: Int? = null, - @SerialId(21) var groupUin: Long? = null, - @SerialId(22) val groupCurMsgSeq: Int? = null, - @SerialId(23) val groupLastMsgTime: Int? = null, - @SerialId(24) val ingGroupQuestion: String? = null, - @SerialId(25) val ingGroupAnswer: String? = null, - @SerialId(26) val groupVisitorMaxNum: Int? = null, - @SerialId(27) val groupVisitorCurNum: Int? = null, - @SerialId(28) val levelNameSeq: Int? = null, - @SerialId(29) val groupAdminMaxNum: Int? = null, - @SerialId(30) val groupAioSkinTimestamp: Int? = null, - @SerialId(31) val groupBoardSkinTimestamp: Int? = null, - @SerialId(32) val ingGroupAioSkinUrl: String? = null, - @SerialId(33) val ingGroupBoardSkinUrl: String? = null, - @SerialId(34) val groupCoverSkinTimestamp: Int? = null, - @SerialId(35) val ingGroupCoverSkinUrl: String? = null, - @SerialId(36) val groupGrade: Int? = null, - @SerialId(37) val activeMemberNum: Int? = null, - @SerialId(38) val certificationType: Int? = null, - @SerialId(39) val ingCertificationText: String? = null, - @SerialId(40) val ingGroupRichFingerMemo: String? = null, - @SerialId(41) val tagRecord: List? = null, - @SerialId(42) val groupGeoInfo: GroupGeoInfo? = null, - @SerialId(43) val headPortraitSeq: Int? = null, - @SerialId(44) val msgHeadPortrait: GroupHeadPortrait? = null, - @SerialId(45) val shutupTimestamp: Int? = null, - @SerialId(46) val shutupTimestampMe: Int? = null, - @SerialId(47) val createSourceFlag: Int? = null, - @SerialId(48) val cmduinMsgSeq: Int? = null, - @SerialId(49) val cmduinJoinTime: Int? = null, - @SerialId(50) val cmduinUinFlag: Int? = null, - @SerialId(51) val cmduinFlagEx: Int? = null, - @SerialId(52) val cmduinNewMobileFlag: Int? = null, - @SerialId(53) val cmduinReadMsgSeq: Int? = null, - @SerialId(54) val cmduinLastMsgTime: Int? = null, - @SerialId(55) val groupTypeFlag: Int? = null, - @SerialId(56) val appPrivilegeFlag: Int? = null, - @SerialId(57) val stGroupExInfo: GroupExInfoOnly? = null, - @SerialId(58) val groupSecLevel: Int? = null, - @SerialId(59) val groupSecLevelInfo: Int? = null, - @SerialId(60) val cmduinPrivilege: Int? = null, - @SerialId(61) val ingPoidInfo: ByteArray? = null, - @SerialId(62) val cmduinFlagEx2: Int? = null, - @SerialId(63) val confUin: Long? = null, - @SerialId(64) val confMaxMsgSeq: Int? = null, - @SerialId(65) val confToGroupTime: Int? = null, - @SerialId(66) val passwordRedbagTime: Int? = null, - @SerialId(67) val subscriptionUin: Long? = null, - @SerialId(68) val memberListChangeSeq: Int? = null, - @SerialId(69) val membercardSeq: Int? = null, - @SerialId(70) val rootId: Long? = null, - @SerialId(71) val parentId: Long? = null, - @SerialId(72) val teamSeq: Int? = null, - @SerialId(73) val historyMsgBeginTime: Long? = null, - @SerialId(74) val inviteNoAuthNumLimit: Long? = null, - @SerialId(75) val cmduinHistoryMsgSeq: Int? = null, - @SerialId(76) val cmduinJoinMsgSeq: Int? = null, - @SerialId(77) val groupFlagext3: Int? = null, - @SerialId(78) val groupOpenAppid: Int? = null, - @SerialId(79) val isConfGroup: Int? = null, - @SerialId(80) val isModifyConfGroupFace: Int? = null, - @SerialId(81) val isModifyConfGroupName: Int? = null, - @SerialId(82) val noFingerOpenFlag: Int? = null, - @SerialId(83) val noCodeFingerOpenFlag: Int? = null, - @SerialId(84) val autoAgreeJoinGroupUserNumForNormalGroup: Int? = null, - @SerialId(85) val autoAgreeJoinGroupUserNumForConfGroup: Int? = null, - @SerialId(86) val isAllowConfGroupMemberNick: Int? = null, - @SerialId(87) val isAllowConfGroupMemberAtAll: Int? = null, - @SerialId(88) val isAllowConfGroupMemberModifyGroupName: Int? = null, - @SerialId(89) val longGroupName: String? = null, - @SerialId(90) val cmduinJoinRealMsgSeq: Int? = null, - @SerialId(91) val isGroupFreeze: Int? = null, - @SerialId(92) val msgLimitFrequency: Int? = null, - @SerialId(93) val joinGroupAuth: ByteArray? = null, - @SerialId(94) val hlGuildAppid: Int? = null, - @SerialId(95) val hlGuildSubType: Int? = null, - @SerialId(96) val hlGuildOrgid: Int? = null, - @SerialId(97) val isAllowHlGuildBinary: Int? = null, - @SerialId(98) val cmduinRingtoneId: Int? = null, - @SerialId(99) val groupFlagext4: Int? = null, - @SerialId(100) val groupFreezeReason: Int? = null, - @SerialId(101) var groupCode: Long? = null // mirai 添加 + @ProtoId(1) val groupOwner: Long? = null, + @ProtoId(2) val groupCreateTime: Int? = null, + @ProtoId(3) val groupFlag: Int? = null, + @ProtoId(4) val groupFlagExt: Int? = null, + @ProtoId(5) val groupMemberMaxNum: Int? = null, + @ProtoId(6) val groupMemberNum: Int? = null, + @ProtoId(7) val groupOption: Int? = null, + @ProtoId(8) val groupClassExt: Int? = null, + @ProtoId(9) val groupSpecialClass: Int? = null, + @ProtoId(10) val groupLevel: Int? = null, + @ProtoId(11) val groupFace: Int? = null, + @ProtoId(12) val groupDefaultPage: Int? = null, + @ProtoId(13) val groupInfoSeq: Int? = null, + @ProtoId(14) val groupRoamingTime: Int? = null, + @ProtoId(15) var groupName: String? = null, + @ProtoId(16) var groupMemo: String? = null, + @ProtoId(17) val ingGroupFingerMemo: String? = null, + @ProtoId(18) val ingGroupClassText: String? = null, + @ProtoId(19) val groupAllianceCode: List? = null, + @ProtoId(20) val groupExtraAdmNum: Int? = null, + @ProtoId(21) var groupUin: Long? = null, + @ProtoId(22) val groupCurMsgSeq: Int? = null, + @ProtoId(23) val groupLastMsgTime: Int? = null, + @ProtoId(24) val ingGroupQuestion: String? = null, + @ProtoId(25) val ingGroupAnswer: String? = null, + @ProtoId(26) val groupVisitorMaxNum: Int? = null, + @ProtoId(27) val groupVisitorCurNum: Int? = null, + @ProtoId(28) val levelNameSeq: Int? = null, + @ProtoId(29) val groupAdminMaxNum: Int? = null, + @ProtoId(30) val groupAioSkinTimestamp: Int? = null, + @ProtoId(31) val groupBoardSkinTimestamp: Int? = null, + @ProtoId(32) val ingGroupAioSkinUrl: String? = null, + @ProtoId(33) val ingGroupBoardSkinUrl: String? = null, + @ProtoId(34) val groupCoverSkinTimestamp: Int? = null, + @ProtoId(35) val ingGroupCoverSkinUrl: String? = null, + @ProtoId(36) val groupGrade: Int? = null, + @ProtoId(37) val activeMemberNum: Int? = null, + @ProtoId(38) val certificationType: Int? = null, + @ProtoId(39) val ingCertificationText: String? = null, + @ProtoId(40) val ingGroupRichFingerMemo: String? = null, + @ProtoId(41) val tagRecord: List? = null, + @ProtoId(42) val groupGeoInfo: GroupGeoInfo? = null, + @ProtoId(43) val headPortraitSeq: Int? = null, + @ProtoId(44) val msgHeadPortrait: GroupHeadPortrait? = null, + @ProtoId(45) val shutupTimestamp: Int? = null, + @ProtoId(46) val shutupTimestampMe: Int? = null, + @ProtoId(47) val createSourceFlag: Int? = null, + @ProtoId(48) val cmduinMsgSeq: Int? = null, + @ProtoId(49) val cmduinJoinTime: Int? = null, + @ProtoId(50) val cmduinUinFlag: Int? = null, + @ProtoId(51) val cmduinFlagEx: Int? = null, + @ProtoId(52) val cmduinNewMobileFlag: Int? = null, + @ProtoId(53) val cmduinReadMsgSeq: Int? = null, + @ProtoId(54) val cmduinLastMsgTime: Int? = null, + @ProtoId(55) val groupTypeFlag: Int? = null, + @ProtoId(56) val appPrivilegeFlag: Int? = null, + @ProtoId(57) val stGroupExInfo: GroupExInfoOnly? = null, + @ProtoId(58) val groupSecLevel: Int? = null, + @ProtoId(59) val groupSecLevelInfo: Int? = null, + @ProtoId(60) val cmduinPrivilege: Int? = null, + @ProtoId(61) val ingPoidInfo: ByteArray? = null, + @ProtoId(62) val cmduinFlagEx2: Int? = null, + @ProtoId(63) val confUin: Long? = null, + @ProtoId(64) val confMaxMsgSeq: Int? = null, + @ProtoId(65) val confToGroupTime: Int? = null, + @ProtoId(66) val passwordRedbagTime: Int? = null, + @ProtoId(67) val subscriptionUin: Long? = null, + @ProtoId(68) val memberListChangeSeq: Int? = null, + @ProtoId(69) val membercardSeq: Int? = null, + @ProtoId(70) val rootId: Long? = null, + @ProtoId(71) val parentId: Long? = null, + @ProtoId(72) val teamSeq: Int? = null, + @ProtoId(73) val historyMsgBeginTime: Long? = null, + @ProtoId(74) val inviteNoAuthNumLimit: Long? = null, + @ProtoId(75) val cmduinHistoryMsgSeq: Int? = null, + @ProtoId(76) val cmduinJoinMsgSeq: Int? = null, + @ProtoId(77) val groupFlagext3: Int? = null, + @ProtoId(78) val groupOpenAppid: Int? = null, + @ProtoId(79) val isConfGroup: Int? = null, + @ProtoId(80) val isModifyConfGroupFace: Int? = null, + @ProtoId(81) val isModifyConfGroupName: Int? = null, + @ProtoId(82) val noFingerOpenFlag: Int? = null, + @ProtoId(83) val noCodeFingerOpenFlag: Int? = null, + @ProtoId(84) val autoAgreeJoinGroupUserNumForNormalGroup: Int? = null, + @ProtoId(85) val autoAgreeJoinGroupUserNumForConfGroup: Int? = null, + @ProtoId(86) val isAllowConfGroupMemberNick: Int? = null, + @ProtoId(87) val isAllowConfGroupMemberAtAll: Int? = null, + @ProtoId(88) val isAllowConfGroupMemberModifyGroupName: Int? = null, + @ProtoId(89) val longGroupName: String? = null, + @ProtoId(90) val cmduinJoinRealMsgSeq: Int? = null, + @ProtoId(91) val isGroupFreeze: Int? = null, + @ProtoId(92) val msgLimitFrequency: Int? = null, + @ProtoId(93) val joinGroupAuth: ByteArray? = null, + @ProtoId(94) val hlGuildAppid: Int? = null, + @ProtoId(95) val hlGuildSubType: Int? = null, + @ProtoId(96) val hlGuildOrgid: Int? = null, + @ProtoId(97) val isAllowHlGuildBinary: Int? = null, + @ProtoId(98) val cmduinRingtoneId: Int? = null, + @ProtoId(99) val groupFlagext4: Int? = null, + @ProtoId(100) val groupFreezeReason: Int? = null, + @ProtoId(101) var groupCode: Long? = null // mirai 添加 ) : ProtoBuf @Serializable class GroupHeadPortraitInfo( - @SerialId(1) val uint32PicId: Int = 0, - @SerialId(2) val leftX: Int = 0, - @SerialId(3) val leftY: Int = 0, - @SerialId(4) val rightX: Int = 0, - @SerialId(5) val rightY: Int = 0 + @ProtoId(1) val uint32PicId: Int = 0, + @ProtoId(2) val leftX: Int = 0, + @ProtoId(3) val leftY: Int = 0, + @ProtoId(4) val rightX: Int = 0, + @ProtoId(5) val rightY: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val stzrspgroupinfo: List? = null, - @SerialId(2) val errorinfo: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val stzrspgroupinfo: List? = null, + @ProtoId(2) val errorinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val appid: Int = 0, - @SerialId(2) val stzreqgroupinfo: List? = null, - @SerialId(3) val pcClientVersion: Int = 0 + @ProtoId(1) val appid: Int = 0, + @ProtoId(2) val stzreqgroupinfo: List? = null, + @ProtoId(3) val pcClientVersion: Int = 0 ) : ProtoBuf @Serializable class GroupHeadPortrait( - @SerialId(1) val picCnt: Int = 0, - @SerialId(2) val msgInfo: List? = null, - @SerialId(3) val defaultId: Int = 0, - @SerialId(4) val verifyingPicCnt: Int = 0, - @SerialId(5) val msgVerifyingpicInfo: List? = null + @ProtoId(1) val picCnt: Int = 0, + @ProtoId(2) val msgInfo: List? = null, + @ProtoId(3) val defaultId: Int = 0, + @ProtoId(4) val verifyingPicCnt: Int = 0, + @ProtoId(5) val msgVerifyingpicInfo: List? = null ) : ProtoBuf } @@ -305,79 +305,79 @@ class Oidb0x88d : ProtoBuf { class Oidb0x89a : ProtoBuf { @Serializable class GroupNewGuidelinesInfo( - @SerialId(1) val boolEnabled: Boolean = false, - @SerialId(2) val ingContent: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val boolEnabled: Boolean = false, + @ProtoId(2) val ingContent: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class Groupinfo( - @SerialId(1) val groupExtAdmNum: Int? = null, - @SerialId(2) val flag: Int? = null, - @SerialId(3) val ingGroupName: ByteArray? = null, - @SerialId(4) val ingGroupMemo: ByteArray? = null, - @SerialId(5) val ingGroupFingerMemo: ByteArray? = null, - @SerialId(6) val ingGroupAioSkinUrl: ByteArray? = null, - @SerialId(7) val ingGroupBoardSkinUrl: ByteArray? = null, - @SerialId(8) val ingGroupCoverSkinUrl: ByteArray? = null, - @SerialId(9) val groupGrade: Int? = null, - @SerialId(10) val activeMemberNum: Int? = null, - @SerialId(11) val certificationType: Int? = null, - @SerialId(12) val ingCertificationText: ByteArray? = null, - @SerialId(13) val ingGroupRichFingerMemo: ByteArray? = null, - @SerialId(14) val stGroupNewguidelines: GroupNewGuidelinesInfo? = null, - @SerialId(15) val groupFace: Int? = null, - @SerialId(16) val addOption: Int? = null, - @SerialId(17) val shutupTime: Int? = null, - @SerialId(18) val groupTypeFlag: Int? = null, - @SerialId(19) val stringGroupTag: List? = null, - @SerialId(20) val msgGroupGeoInfo: GroupGeoInfo? = null, - @SerialId(21) val groupClassExt: Int? = null, - @SerialId(22) val ingGroupClassText: ByteArray? = null, - @SerialId(23) val appPrivilegeFlag: Int? = null, - @SerialId(24) val appPrivilegeMask: Int? = null, - @SerialId(25) val stGroupExInfo: GroupExInfoOnly? = null, - @SerialId(26) val groupSecLevel: Int? = null, - @SerialId(27) val groupSecLevelInfo: Int? = null, - @SerialId(28) val subscriptionUin: Long? = null, - @SerialId(29) val allowMemberInvite: Int? = null, - @SerialId(30) val ingGroupQuestion: ByteArray? = null, - @SerialId(31) val ingGroupAnswer: ByteArray? = null, - @SerialId(32) val groupFlagext3: Int? = null, - @SerialId(33) val groupFlagext3Mask: Int? = null, - @SerialId(34) val groupOpenAppid: Int? = null, - @SerialId(35) val noFingerOpenFlag: Int? = null, - @SerialId(36) val noCodeFingerOpenFlag: Int? = null, - @SerialId(37) val rootId: Long? = null, - @SerialId(38) val msgLimitFrequency: Int? = null + @ProtoId(1) val groupExtAdmNum: Int? = null, + @ProtoId(2) val flag: Int? = null, + @ProtoId(3) val ingGroupName: ByteArray? = null, + @ProtoId(4) val ingGroupMemo: ByteArray? = null, + @ProtoId(5) val ingGroupFingerMemo: ByteArray? = null, + @ProtoId(6) val ingGroupAioSkinUrl: ByteArray? = null, + @ProtoId(7) val ingGroupBoardSkinUrl: ByteArray? = null, + @ProtoId(8) val ingGroupCoverSkinUrl: ByteArray? = null, + @ProtoId(9) val groupGrade: Int? = null, + @ProtoId(10) val activeMemberNum: Int? = null, + @ProtoId(11) val certificationType: Int? = null, + @ProtoId(12) val ingCertificationText: ByteArray? = null, + @ProtoId(13) val ingGroupRichFingerMemo: ByteArray? = null, + @ProtoId(14) val stGroupNewguidelines: GroupNewGuidelinesInfo? = null, + @ProtoId(15) val groupFace: Int? = null, + @ProtoId(16) val addOption: Int? = null, + @ProtoId(17) val shutupTime: Int? = null, + @ProtoId(18) val groupTypeFlag: Int? = null, + @ProtoId(19) val stringGroupTag: List? = null, + @ProtoId(20) val msgGroupGeoInfo: GroupGeoInfo? = null, + @ProtoId(21) val groupClassExt: Int? = null, + @ProtoId(22) val ingGroupClassText: ByteArray? = null, + @ProtoId(23) val appPrivilegeFlag: Int? = null, + @ProtoId(24) val appPrivilegeMask: Int? = null, + @ProtoId(25) val stGroupExInfo: GroupExInfoOnly? = null, + @ProtoId(26) val groupSecLevel: Int? = null, + @ProtoId(27) val groupSecLevelInfo: Int? = null, + @ProtoId(28) val subscriptionUin: Long? = null, + @ProtoId(29) val allowMemberInvite: Int? = null, + @ProtoId(30) val ingGroupQuestion: ByteArray? = null, + @ProtoId(31) val ingGroupAnswer: ByteArray? = null, + @ProtoId(32) val groupFlagext3: Int? = null, + @ProtoId(33) val groupFlagext3Mask: Int? = null, + @ProtoId(34) val groupOpenAppid: Int? = null, + @ProtoId(35) val noFingerOpenFlag: Int? = null, + @ProtoId(36) val noCodeFingerOpenFlag: Int? = null, + @ProtoId(37) val rootId: Long? = null, + @ProtoId(38) val msgLimitFrequency: Int? = null ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val errorinfo: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val errorinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class GroupExInfoOnly( - @SerialId(1) val tribeId: Int = 0, - @SerialId(2) val moneyForAddGroup: Int = 0 + @ProtoId(1) val tribeId: Int = 0, + @ProtoId(2) val moneyForAddGroup: Int = 0 ) : ProtoBuf @Serializable class GroupGeoInfo( - @SerialId(1) val cityId: Int = 0, - @SerialId(2) val longtitude: Long = 0L, - @SerialId(3) val latitude: Long = 0L, - @SerialId(4) val ingGeoContent: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val poiId: Long = 0L + @ProtoId(1) val cityId: Int = 0, + @ProtoId(2) val longtitude: Long = 0L, + @ProtoId(3) val latitude: Long = 0L, + @ProtoId(4) val ingGeoContent: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val poiId: Long = 0L ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val groupCode: Long = 0L, - @SerialId(2) val stGroupInfo: Groupinfo? = null, - @SerialId(3) val originalOperatorUin: Long = 0L, - @SerialId(4) val reqGroupOpenAppid: Int = 0 + @ProtoId(1) val groupCode: Long = 0L, + @ProtoId(2) val stGroupInfo: Groupinfo? = null, + @ProtoId(3) val originalOperatorUin: Long = 0L, + @ProtoId(4) val reqGroupOpenAppid: Int = 0 ) : ProtoBuf } @@ -385,50 +385,50 @@ class Oidb0x89a : ProtoBuf { class Cmd0x7cb : ProtoBuf { @Serializable class ConfigItem( - @SerialId(1) val id: Int = 0, - @SerialId(2) val config: String = "" + @ProtoId(1) val id: Int = 0, + @ProtoId(2) val config: String = "" ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val timeStamp: Int = 0, - @SerialId(2) val timeGap: Int = 0, - @SerialId(3) val commentConfigs: List? = null, - @SerialId(4) val attendTipsToA: String = "", - @SerialId(5) val firstMsgTips: String = "", - @SerialId(6) val cancleConfig: List? = null, - @SerialId(7) val msgDateRequest: DateRequest? = null, - @SerialId(8) val msgHotLocale: List? = null,//List - @SerialId(9) val msgTopicList: List? = null, - @SerialId(10) val travelMsgTips: String = "", - @SerialId(11) val travelProfileTips: String = "", - @SerialId(12) val travelAttenTips: String = "", - @SerialId(13) val topicDefault: Int = 0 + @ProtoId(1) val timeStamp: Int = 0, + @ProtoId(2) val timeGap: Int = 0, + @ProtoId(3) val commentConfigs: List? = null, + @ProtoId(4) val attendTipsToA: String = "", + @ProtoId(5) val firstMsgTips: String = "", + @ProtoId(6) val cancleConfig: List? = null, + @ProtoId(7) val msgDateRequest: DateRequest? = null, + @ProtoId(8) val msgHotLocale: List? = null,//List + @ProtoId(9) val msgTopicList: List? = null, + @ProtoId(10) val travelMsgTips: String = "", + @ProtoId(11) val travelProfileTips: String = "", + @ProtoId(12) val travelAttenTips: String = "", + @ProtoId(13) val topicDefault: Int = 0 ) : ProtoBuf @Serializable class CommentConfig( - @SerialId(1) val appointSubject: Int = 0, - @SerialId(2) val msgConfigs: List? = null + @ProtoId(1) val appointSubject: Int = 0, + @ProtoId(2) val msgConfigs: List? = null ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val timeStamp: Int = 0 + @ProtoId(1) val timeStamp: Int = 0 ) : ProtoBuf @Serializable class DateRequest( - @SerialId(1) val time: Int = 0, - @SerialId(2) val errMsg: String = "" + @ProtoId(1) val time: Int = 0, + @ProtoId(2) val errMsg: String = "" ) : ProtoBuf @Serializable class TopicConfig( - @SerialId(1) val topicId: Int = 0, - @SerialId(2) val topicName: String = "", - @SerialId(3) val deadline: Int = 0, - @SerialId(4) val errDeadline: String = "" + @ProtoId(1) val topicId: Int = 0, + @ProtoId(2) val topicName: String = "", + @ProtoId(3) val deadline: Int = 0, + @ProtoId(4) val errDeadline: String = "" ) : ProtoBuf } @@ -436,18 +436,18 @@ class Cmd0x7cb : ProtoBuf { class Oidb0x87a : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val country: String = "", - @SerialId(2) val telephone: String = "", - @SerialId(3) val resendInterval: Int = 0, - @SerialId(4) val guid: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val country: String = "", + @ProtoId(2) val telephone: String = "", + @ProtoId(3) val resendInterval: Int = 0, + @ProtoId(4) val guid: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val country: String = "", - @SerialId(2) val telephone: String = "", - @SerialId(3) val guid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val enumButype: Int /* enum */ = 0 + @ProtoId(1) val country: String = "", + @ProtoId(2) val telephone: String = "", + @ProtoId(3) val guid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val enumButype: Int /* enum */ = 0 ) : ProtoBuf } @@ -455,42 +455,42 @@ class Oidb0x87a : ProtoBuf { class GroupAppPb : ProtoBuf { @Serializable class ClientInfo( - @SerialId(1) val platform: Int = 0, - @SerialId(2) val version: String = "" + @ProtoId(1) val platform: Int = 0, + @ProtoId(2) val version: String = "" ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val fullList: AppList? = null, - @SerialId(2) val groupGrayList: AppList? = null, - @SerialId(3) val redPointList: AppList? = null, - @SerialId(4) val cacheInterval: Int = 0 + @ProtoId(1) val fullList: AppList? = null, + @ProtoId(2) val groupGrayList: AppList? = null, + @ProtoId(3) val redPointList: AppList? = null, + @ProtoId(4) val cacheInterval: Int = 0 ) : ProtoBuf @Serializable class AppList( - @SerialId(1) val hash: String = "", - @SerialId(2) val infos: List? = null + @ProtoId(1) val hash: String = "", + @ProtoId(2) val infos: List? = null ) : ProtoBuf @Serializable class AppInfo( - @SerialId(1) val appid: Int = 0, - @SerialId(2) val icon: String = "", - @SerialId(3) val name: String = "", - @SerialId(4) val url: String = "", - @SerialId(5) val isGray: Int = 0, - @SerialId(6) val iconSimpleDay: String = "", - @SerialId(7) val iconSimpleNight: String = "" + @ProtoId(1) val appid: Int = 0, + @ProtoId(2) val icon: String = "", + @ProtoId(3) val name: String = "", + @ProtoId(4) val url: String = "", + @ProtoId(5) val isGray: Int = 0, + @ProtoId(6) val iconSimpleDay: String = "", + @ProtoId(7) val iconSimpleNight: String = "" ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val client: ClientInfo? = null, - @SerialId(2) val groupId: Long = 0L, - @SerialId(3) val groupType: Int = 0, - @SerialId(4) val fullListHash: String = "", - @SerialId(5) val groupGrayListHash: String = "" + @ProtoId(1) val client: ClientInfo? = null, + @ProtoId(2) val groupId: Long = 0L, + @ProtoId(3) val groupType: Int = 0, + @ProtoId(4) val fullListHash: String = "", + @ProtoId(5) val groupGrayListHash: String = "" ) : ProtoBuf } @@ -501,7 +501,7 @@ class Oidb0xc34 : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L + @ProtoId(1) val uin: Long = 0L ) : ProtoBuf } @@ -509,18 +509,18 @@ class Oidb0xc34 : ProtoBuf { class Cmd0x5fd : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val msgComment: AppointDefine.DateComment? = null, - @SerialId(2) val maxFetchCount: Int = 0, - @SerialId(3) val lastCommentId: String = "" + @ProtoId(1) val msgComment: AppointDefine.DateComment? = null, + @ProtoId(2) val maxFetchCount: Int = 0, + @ProtoId(3) val lastCommentId: String = "" ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val msgComment: List? = null, - @SerialId(2) val errorTips: String = "", - @SerialId(3) val clearCacheFlag: Int = 0, - @SerialId(4) val commentWording: String = "", - @SerialId(5) val commentNum: Int = 0 + @ProtoId(1) val msgComment: List? = null, + @ProtoId(2) val errorTips: String = "", + @ProtoId(3) val clearCacheFlag: Int = 0, + @ProtoId(4) val commentWording: String = "", + @ProtoId(5) val commentNum: Int = 0 ) : ProtoBuf } @@ -528,62 +528,62 @@ class Cmd0x5fd : ProtoBuf { class Oidb0xbcb : ProtoBuf { @Serializable class CheckUrlReqItem( - @SerialId(1) val url: String = "", - @SerialId(2) val refer: String = "", - @SerialId(3) val plateform: String = "", - @SerialId(4) val qqPfTo: String = "", - @SerialId(5) val msgType: Int = 0, - @SerialId(6) val msgFrom: Int = 0, - @SerialId(7) val msgChatid: Long = 0L, - @SerialId(8) val serviceType: Long = 0L, - @SerialId(9) val sendUin: Long = 0L, - @SerialId(10) val reqType: String = "" + @ProtoId(1) val url: String = "", + @ProtoId(2) val refer: String = "", + @ProtoId(3) val plateform: String = "", + @ProtoId(4) val qqPfTo: String = "", + @ProtoId(5) val msgType: Int = 0, + @ProtoId(6) val msgFrom: Int = 0, + @ProtoId(7) val msgChatid: Long = 0L, + @ProtoId(8) val serviceType: Long = 0L, + @ProtoId(9) val sendUin: Long = 0L, + @ProtoId(10) val reqType: String = "" ) : ProtoBuf @Serializable class CheckUrlRsp( - @SerialId(1) val results: List? = null, - @SerialId(2) val nextReqDuration: Int = 0 + @ProtoId(1) val results: List? = null, + @ProtoId(2) val nextReqDuration: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(9) val notUseCache: Int = 0, - @SerialId(10) val checkUrlReq: CheckUrlReq? = null + @ProtoId(9) val notUseCache: Int = 0, + @ProtoId(10) val checkUrlReq: CheckUrlReq? = null ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val wording: String = "", - @SerialId(10) val checkUrlRsp: CheckUrlRsp? = null + @ProtoId(1) val wording: String = "", + @ProtoId(10) val checkUrlRsp: CheckUrlRsp? = null ) : ProtoBuf @Serializable class CheckUrlReq( - @SerialId(1) val url: List = listOf(), - @SerialId(2) val refer: String = "", - @SerialId(3) val plateform: String = "", - @SerialId(4) val qqPfTo: String = "", - @SerialId(5) val msgType: Int = 0, - @SerialId(6) val msgFrom: Int = 0, - @SerialId(7) val msgChatid: Long = 0L, - @SerialId(8) val serviceType: Long = 0L, - @SerialId(9) val sendUin: Long = 0L, - @SerialId(10) val reqType: String = "", - @SerialId(11) val originalUrl: String = "" + @ProtoId(1) val url: List = listOf(), + @ProtoId(2) val refer: String = "", + @ProtoId(3) val plateform: String = "", + @ProtoId(4) val qqPfTo: String = "", + @ProtoId(5) val msgType: Int = 0, + @ProtoId(6) val msgFrom: Int = 0, + @ProtoId(7) val msgChatid: Long = 0L, + @ProtoId(8) val serviceType: Long = 0L, + @ProtoId(9) val sendUin: Long = 0L, + @ProtoId(10) val reqType: String = "", + @ProtoId(11) val originalUrl: String = "" ) : ProtoBuf @Serializable class UrlCheckResult( - @SerialId(1) val url: String = "", - @SerialId(2) val result: Int = 0, - @SerialId(3) val jumpResult: Int = 0, - @SerialId(4) val jumpUrl: String = "", - @SerialId(5) val level: Int = 0, - @SerialId(6) val subLevel: Int = 0, - @SerialId(7) val umrtype: Int = 0, - @SerialId(8) val retFrom: Int = 0, - @SerialId(9) val operationBit: Long = 0L + @ProtoId(1) val url: String = "", + @ProtoId(2) val result: Int = 0, + @ProtoId(3) val jumpResult: Int = 0, + @ProtoId(4) val jumpUrl: String = "", + @ProtoId(5) val level: Int = 0, + @ProtoId(6) val subLevel: Int = 0, + @ProtoId(7) val umrtype: Int = 0, + @ProtoId(8) val retFrom: Int = 0, + @ProtoId(9) val operationBit: Long = 0L ) : ProtoBuf } @@ -591,14 +591,14 @@ class Oidb0xbcb : ProtoBuf { class Oidb0xbfe : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val receiveStatus: Int = 0, - @SerialId(2) val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val flag: Int = 0 + @ProtoId(1) val receiveStatus: Int = 0, + @ProtoId(2) val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val flag: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L + @ProtoId(1) val uin: Long = 0L ) : ProtoBuf } @@ -606,26 +606,26 @@ class Oidb0xbfe : ProtoBuf { class Oidb0xbe8 : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val enumOpCode: Int /* enum */ = 1, - @SerialId(3) val rspOfPopupFlag: Int = 0, - @SerialId(4) val popupCountNow: Int = 0 + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val enumOpCode: Int /* enum */ = 1, + @ProtoId(3) val rspOfPopupFlag: Int = 0, + @ProtoId(4) val popupCountNow: Int = 0 ) : ProtoBuf @Serializable class PopupResult( - @SerialId(1) val popupResult: Int = 0, - @SerialId(2) val popupFieldid: Int = 0 + @ProtoId(1) val popupResult: Int = 0, + @ProtoId(2) val popupFieldid: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val enumOpCode: Int /* enum */ = 1, - @SerialId(3) val reqOfPopupFlag: Int = 0, - @SerialId(4) val rstOfPopupFlag: Int = 0, - @SerialId(5) val mqq808WelcomepageFlag: Int = 0, - @SerialId(6) val msgPopupResult: List? = null + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val enumOpCode: Int /* enum */ = 1, + @ProtoId(3) val reqOfPopupFlag: Int = 0, + @ProtoId(4) val rstOfPopupFlag: Int = 0, + @ProtoId(5) val mqq808WelcomepageFlag: Int = 0, + @ProtoId(6) val msgPopupResult: List? = null ) : ProtoBuf } @@ -633,45 +633,45 @@ class Oidb0xbe8 : ProtoBuf { class Cmd0x7de : ProtoBuf { @Serializable class UserProfile( - @SerialId(1) val msgPublisherInfo: AppointDefine.PublisherInfo? = null, - @SerialId(2) val msgAppointsInfo: AppointDefine.AppointInfo? = null, - @SerialId(3) val msgVistorInfo: List? = null + @ProtoId(1) val msgPublisherInfo: AppointDefine.PublisherInfo? = null, + @ProtoId(2) val msgAppointsInfo: AppointDefine.AppointInfo? = null, + @ProtoId(3) val msgVistorInfo: List? = null ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val msgHead: BusiRespHead? = null, - @SerialId(2) val msgUserList: List? = null, - @SerialId(3) val ended: Int = 0, - @SerialId(4) val cookie: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val msgHead: BusiRespHead? = null, + @ProtoId(2) val msgUserList: List? = null, + @ProtoId(3) val ended: Int = 0, + @ProtoId(4) val cookie: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class BusiRespHead( - @SerialId(1) val int32Version: Int = 1, - @SerialId(2) val int32Seq: Int = 0, - @SerialId(3) val int32ReplyCode: Int = 0, - @SerialId(4) val result: String = "" + @ProtoId(1) val int32Version: Int = 1, + @ProtoId(2) val int32Seq: Int = 0, + @ProtoId(3) val int32ReplyCode: Int = 0, + @ProtoId(4) val result: String = "" ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val msgHead: BusiReqHead? = null, - @SerialId(2) val msgLbsInfo: AppointDefine.LBSInfo? = null, - @SerialId(3) val time: Int = 0, - @SerialId(4) val subject: Int = 0, - @SerialId(5) val gender: Int = 0, - @SerialId(6) val ageLow: Int = 0, - @SerialId(7) val ageUp: Int = 0, - @SerialId(8) val profession: Int = 0, - @SerialId(9) val cookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val msgDestination: AppointDefine.LocaleInfo? = null + @ProtoId(1) val msgHead: BusiReqHead? = null, + @ProtoId(2) val msgLbsInfo: AppointDefine.LBSInfo? = null, + @ProtoId(3) val time: Int = 0, + @ProtoId(4) val subject: Int = 0, + @ProtoId(5) val gender: Int = 0, + @ProtoId(6) val ageLow: Int = 0, + @ProtoId(7) val ageUp: Int = 0, + @ProtoId(8) val profession: Int = 0, + @ProtoId(9) val cookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val msgDestination: AppointDefine.LocaleInfo? = null ) : ProtoBuf @Serializable class BusiReqHead( - @SerialId(1) val int32Version: Int = 1, - @SerialId(2) val int32Seq: Int = 0 + @ProtoId(1) val int32Version: Int = 1, + @ProtoId(2) val int32Seq: Int = 0 ) : ProtoBuf } @@ -679,37 +679,37 @@ class Cmd0x7de : ProtoBuf { class Cmd0x7a8 : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val reqUin: Long = 0L, - @SerialId(11) val onlyObtained: Int = 0, - @SerialId(12) val readReport: Int = 0, - @SerialId(13) val sortType: Int = 0, - @SerialId(14) val onlyNew: Int = 0, - @SerialId(15) val filterMedalIds: List? = null, - @SerialId(16) val onlySummary: Int = 0, - @SerialId(17) val doScan: Int = 0, - @SerialId(18) val startTimestamp: Int = 0 + @ProtoId(1) val reqUin: Long = 0L, + @ProtoId(11) val onlyObtained: Int = 0, + @ProtoId(12) val readReport: Int = 0, + @ProtoId(13) val sortType: Int = 0, + @ProtoId(14) val onlyNew: Int = 0, + @ProtoId(15) val filterMedalIds: List? = null, + @ProtoId(16) val onlySummary: Int = 0, + @ProtoId(17) val doScan: Int = 0, + @ProtoId(18) val startTimestamp: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val nick: String = "", - @SerialId(2) val metalRank: Int = 0, - @SerialId(3) val friCount: Int = 0, - @SerialId(4) val metalCount: Int = 0, - @SerialId(5) val metalTotal: Int = 0, - @SerialId(6) val msgMedal: List? = null, - @SerialId(8) val totalPoint: Int = 0, - @SerialId(9) val int32NewCount: Int = 0, - @SerialId(10) val int32UpgradeCount: Int = 0, - @SerialId(11) val promptParams: String = "", - @SerialId(12) val now: Int = 0 + @ProtoId(1) val nick: String = "", + @ProtoId(2) val metalRank: Int = 0, + @ProtoId(3) val friCount: Int = 0, + @ProtoId(4) val metalCount: Int = 0, + @ProtoId(5) val metalTotal: Int = 0, + @ProtoId(6) val msgMedal: List? = null, + @ProtoId(8) val totalPoint: Int = 0, + @ProtoId(9) val int32NewCount: Int = 0, + @ProtoId(10) val int32UpgradeCount: Int = 0, + @ProtoId(11) val promptParams: String = "", + @ProtoId(12) val now: Int = 0 ) : ProtoBuf @Serializable class MedalNews( - @SerialId(1) val friUin: Long = 0L, - @SerialId(2) val friNick: String = "", - @SerialId(3) val msgMedal: Common.MedalInfo? = null + @ProtoId(1) val friUin: Long = 0L, + @ProtoId(2) val friNick: String = "", + @ProtoId(3) val msgMedal: Common.MedalInfo? = null ) : ProtoBuf } @@ -718,18 +718,18 @@ class Cmd0x7a8 : ProtoBuf { class Cmd0x5fe : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val msgAppointId: AppointDefine.AppointID? = null, - @SerialId(2) val commentId: String = "", - @SerialId(3) val fetchOldCount: Int = 0, - @SerialId(4) val fetchNewCount: Int = 0 + @ProtoId(1) val msgAppointId: AppointDefine.AppointID? = null, + @ProtoId(2) val commentId: String = "", + @ProtoId(3) val fetchOldCount: Int = 0, + @ProtoId(4) val fetchNewCount: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val msgComment: List? = null, - @SerialId(2) val errorTips: String = "", - @SerialId(3) val fetchOldOver: Int = 0, - @SerialId(4) val fetchNewOver: Int = 0 + @ProtoId(1) val msgComment: List? = null, + @ProtoId(2) val errorTips: String = "", + @ProtoId(3) val fetchOldOver: Int = 0, + @ProtoId(4) val fetchNewOver: Int = 0 ) : ProtoBuf } @@ -740,20 +740,20 @@ class Oidb0xc35 : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val msgExposeInfo: List? = null + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val msgExposeInfo: List? = null ) : ProtoBuf @Serializable class ExposeItem( - @SerialId(1) val friend: Long = 0L, - @SerialId(2) val pageId: Int = 0, - @SerialId(3) val entranceId: Int = 0, - @SerialId(4) val actionId: Int = 0, - @SerialId(5) val exposeCount: Int = 0, - @SerialId(6) val exposeTime: Int = 0, - @SerialId(7) val algoBuffer: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val addition: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val friend: Long = 0L, + @ProtoId(2) val pageId: Int = 0, + @ProtoId(3) val entranceId: Int = 0, + @ProtoId(4) val actionId: Int = 0, + @ProtoId(5) val exposeCount: Int = 0, + @ProtoId(6) val exposeTime: Int = 0, + @ProtoId(7) val algoBuffer: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(8) val addition: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @@ -761,15 +761,15 @@ class Oidb0xc35 : ProtoBuf { class Oidb0xc0d : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val completedTaskStamp: Long = 0L, - @SerialId(2) val errMsg: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val completedTaskStamp: Long = 0L, + @ProtoId(2) val errMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val taskType: Int = 0, - @SerialId(3) val taskPoint: Int = 0 + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val taskType: Int = 0, + @ProtoId(3) val taskPoint: Int = 0 ) : ProtoBuf } @@ -777,12 +777,12 @@ class Oidb0xc0d : ProtoBuf { class OidbSso : ProtoBuf { @Serializable class OIDBSSOPkg( - @SerialId(1) val command: Int = 0, - @SerialId(2) val serviceType: Int = 0, - @SerialId(3) val result: Int = 0, - @SerialId(4) val bodybuffer: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val errorMsg: String = "", - @SerialId(6) val clientVersion: String = "" + @ProtoId(1) val command: Int = 0, + @ProtoId(2) val serviceType: Int = 0, + @ProtoId(3) val result: Int = 0, + @ProtoId(4) val bodybuffer: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val errorMsg: String = "", + @ProtoId(6) val clientVersion: String = "" ) : ProtoBuf } @@ -790,15 +790,15 @@ class OidbSso : ProtoBuf { class Cmd0xc83 : ProtoBuf { @Serializable class ReqBody( - @SerialId(101) val fromUin: Long = 0L, - @SerialId(102) val toUin: Long = 0L, - @SerialId(103) val op: Int = 0 + @ProtoId(101) val fromUin: Long = 0L, + @ProtoId(102) val toUin: Long = 0L, + @ProtoId(103) val op: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(101) val result: Int = 0, - @SerialId(102) val retryInterval: Int = 0 + @ProtoId(101) val result: Int = 0, + @ProtoId(102) val retryInterval: Int = 0 ) : ProtoBuf } @@ -806,35 +806,35 @@ class Cmd0xc83 : ProtoBuf { class Cmd0xccb : ProtoBuf { @Serializable class GroupMsgInfo( - @SerialId(1) val msgSeq: Int = 0, - @SerialId(2) val roamFlag: Int = 0 + @ProtoId(1) val msgSeq: Int = 0, + @ProtoId(2) val roamFlag: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val type: Int = 0, - @SerialId(2) val destUin: Long = 0L, - @SerialId(3) val groupCode: Long = 0L, - @SerialId(4) val c2cMsg: List? = null, - @SerialId(5) val groupMsg: List? = null + @ProtoId(1) val type: Int = 0, + @ProtoId(2) val destUin: Long = 0L, + @ProtoId(3) val groupCode: Long = 0L, + @ProtoId(4) val c2cMsg: List? = null, + @ProtoId(5) val groupMsg: List? = null ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val type: Int = 0, - @SerialId(2) val destUin: Long = 0L, - @SerialId(3) val groupCode: Long = 0L, - @SerialId(4) val c2cMsg: List? = null, - @SerialId(5) val groupMsg: List? = null, - @SerialId(6) val resId: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val type: Int = 0, + @ProtoId(2) val destUin: Long = 0L, + @ProtoId(3) val groupCode: Long = 0L, + @ProtoId(4) val c2cMsg: List? = null, + @ProtoId(5) val groupMsg: List? = null, + @ProtoId(6) val resId: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class C2cMsgInfo( - @SerialId(1) val msgSeq: Int = 0, - @SerialId(2) val msgTime: Int = 0, - @SerialId(3) val msgRandom: Int = 0, - @SerialId(4) val roamFlag: Int = 0 + @ProtoId(1) val msgSeq: Int = 0, + @ProtoId(2) val msgTime: Int = 0, + @ProtoId(3) val msgRandom: Int = 0, + @ProtoId(4) val roamFlag: Int = 0 ) : ProtoBuf } @@ -842,12 +842,12 @@ class Cmd0xccb : ProtoBuf { class Oidb0xd84 : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val xmitinfo: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val xmitinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val xmitinfo: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val xmitinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @@ -855,103 +855,103 @@ class Oidb0xd84 : ProtoBuf { class Oidb0x5e1 : ProtoBuf { @Serializable class UdcUinData( - @SerialId(1) val uin: Long = 0L, - @SerialId(4) val openid: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20002) val nick: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20003) val country: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20004) val province: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20009) val gender: Int = 0, - @SerialId(20014) val allow: Int = 0, - @SerialId(20015) val faceId: Int = 0, - @SerialId(20020) val city: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20027) val commonPlace1: Int = 0, - @SerialId(20030) val mss3Bitmapextra: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20031) val birthday: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20032) val cityId: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(20033) val lang1: Int = 0, - @SerialId(20034) val lang2: Int = 0, - @SerialId(20035) val lang3: Int = 0, - @SerialId(20041) val cityZoneId: Int = 0, - @SerialId(20056) val oin: Int = 0, - @SerialId(20059) val bubbleId: Int = 0, - @SerialId(21001) val mss2Identity: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(21002) val mss1Service: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(21003) val lflag: Int = 0, - @SerialId(21004) val extFlag: Int = 0, - @SerialId(21006) val basicSvrFlag: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(21007) val basicCliFlag: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(24101) val pengyouRealname: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(24103) val pengyouGender: Int = 0, - @SerialId(24118) val pengyouFlag: Int = 0, - @SerialId(26004) val fullBirthday: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(26005) val fullAge: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(26010) val simpleUpdateTime: Int = 0, - @SerialId(26011) val mssUpdateTime: Int = 0, - @SerialId(27022) val groupMemCreditFlag: Int = 0, - @SerialId(27025) val faceAddonId: Long = 0L, - @SerialId(27026) val musicGene: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(40323) val fileShareBit: Int = 0, - @SerialId(40404) val recommendPrivacyCtrl: Int = 0, - @SerialId(40505) val oldFriendChat: Int = 0, - @SerialId(40602) val businessBit: Int = 0, - @SerialId(41305) val crmBit: Int = 0, - @SerialId(41810) val forbidFileshareBit: Int = 0, - @SerialId(42333) val userLoginGuardFace: Int = 0 + @ProtoId(1) val uin: Long = 0L, + @ProtoId(4) val openid: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20002) val nick: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20003) val country: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20004) val province: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20009) val gender: Int = 0, + @ProtoId(20014) val allow: Int = 0, + @ProtoId(20015) val faceId: Int = 0, + @ProtoId(20020) val city: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20027) val commonPlace1: Int = 0, + @ProtoId(20030) val mss3Bitmapextra: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20031) val birthday: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20032) val cityId: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(20033) val lang1: Int = 0, + @ProtoId(20034) val lang2: Int = 0, + @ProtoId(20035) val lang3: Int = 0, + @ProtoId(20041) val cityZoneId: Int = 0, + @ProtoId(20056) val oin: Int = 0, + @ProtoId(20059) val bubbleId: Int = 0, + @ProtoId(21001) val mss2Identity: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(21002) val mss1Service: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(21003) val lflag: Int = 0, + @ProtoId(21004) val extFlag: Int = 0, + @ProtoId(21006) val basicSvrFlag: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(21007) val basicCliFlag: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(24101) val pengyouRealname: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(24103) val pengyouGender: Int = 0, + @ProtoId(24118) val pengyouFlag: Int = 0, + @ProtoId(26004) val fullBirthday: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(26005) val fullAge: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(26010) val simpleUpdateTime: Int = 0, + @ProtoId(26011) val mssUpdateTime: Int = 0, + @ProtoId(27022) val groupMemCreditFlag: Int = 0, + @ProtoId(27025) val faceAddonId: Long = 0L, + @ProtoId(27026) val musicGene: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(40323) val fileShareBit: Int = 0, + @ProtoId(40404) val recommendPrivacyCtrl: Int = 0, + @ProtoId(40505) val oldFriendChat: Int = 0, + @ProtoId(40602) val businessBit: Int = 0, + @ProtoId(41305) val crmBit: Int = 0, + @ProtoId(41810) val forbidFileshareBit: Int = 0, + @ProtoId(42333) val userLoginGuardFace: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(11) val msgUinData: List? = null, - @SerialId(12) val uint64UnfinishedUins: List? = null + @ProtoId(11) val msgUinData: List? = null, + @ProtoId(12) val uint64UnfinishedUins: List? = null ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val uint64Uins: List? = null, - @SerialId(2) val startTime: Int = 0, - @SerialId(3) val maxPackageSize: Int = 0, - @SerialId(4) val bytesOpenid: List? = null, - @SerialId(5) val appid: Int = 0, - @SerialId(20002) val reqNick: Int = 0, - @SerialId(20003) val reqCountry: Int = 0, - @SerialId(20004) val reqProvince: Int = 0, - @SerialId(20009) val reqGender: Int = 0, - @SerialId(20014) val reqAllow: Int = 0, - @SerialId(20015) val reqFaceId: Int = 0, - @SerialId(20020) val reqCity: Int = 0, - @SerialId(20027) val reqCommonPlace1: Int = 0, - @SerialId(20030) val reqMss3Bitmapextra: Int = 0, - @SerialId(20031) val reqBirthday: Int = 0, - @SerialId(20032) val reqCityId: Int = 0, - @SerialId(20033) val reqLang1: Int = 0, - @SerialId(20034) val reqLang2: Int = 0, - @SerialId(20035) val reqLang3: Int = 0, - @SerialId(20041) val reqCityZoneId: Int = 0, - @SerialId(20056) val reqOin: Int = 0, - @SerialId(20059) val reqBubbleId: Int = 0, - @SerialId(21001) val reqMss2Identity: Int = 0, - @SerialId(21002) val reqMss1Service: Int = 0, - @SerialId(21003) val reqLflag: Int = 0, - @SerialId(21004) val reqExtFlag: Int = 0, - @SerialId(21006) val reqBasicSvrFlag: Int = 0, - @SerialId(21007) val reqBasicCliFlag: Int = 0, - @SerialId(24101) val reqPengyouRealname: Int = 0, - @SerialId(24103) val reqPengyouGender: Int = 0, - @SerialId(24118) val reqPengyouFlag: Int = 0, - @SerialId(26004) val reqFullBirthday: Int = 0, - @SerialId(26005) val reqFullAge: Int = 0, - @SerialId(26010) val reqSimpleUpdateTime: Int = 0, - @SerialId(26011) val reqMssUpdateTime: Int = 0, - @SerialId(27022) val reqGroupMemCreditFlag: Int = 0, - @SerialId(27025) val reqFaceAddonId: Int = 0, - @SerialId(27026) val reqMusicGene: Int = 0, - @SerialId(40323) val reqFileShareBit: Int = 0, - @SerialId(40404) val reqRecommendPrivacyCtrlBit: Int = 0, - @SerialId(40505) val reqOldFriendChatBit: Int = 0, - @SerialId(40602) val reqBusinessBit: Int = 0, - @SerialId(41305) val reqCrmBit: Int = 0, - @SerialId(41810) val reqForbidFileshareBit: Int = 0, - @SerialId(42333) val userLoginGuardFace: Int = 0 + @ProtoId(1) val uint64Uins: List? = null, + @ProtoId(2) val startTime: Int = 0, + @ProtoId(3) val maxPackageSize: Int = 0, + @ProtoId(4) val bytesOpenid: List? = null, + @ProtoId(5) val appid: Int = 0, + @ProtoId(20002) val reqNick: Int = 0, + @ProtoId(20003) val reqCountry: Int = 0, + @ProtoId(20004) val reqProvince: Int = 0, + @ProtoId(20009) val reqGender: Int = 0, + @ProtoId(20014) val reqAllow: Int = 0, + @ProtoId(20015) val reqFaceId: Int = 0, + @ProtoId(20020) val reqCity: Int = 0, + @ProtoId(20027) val reqCommonPlace1: Int = 0, + @ProtoId(20030) val reqMss3Bitmapextra: Int = 0, + @ProtoId(20031) val reqBirthday: Int = 0, + @ProtoId(20032) val reqCityId: Int = 0, + @ProtoId(20033) val reqLang1: Int = 0, + @ProtoId(20034) val reqLang2: Int = 0, + @ProtoId(20035) val reqLang3: Int = 0, + @ProtoId(20041) val reqCityZoneId: Int = 0, + @ProtoId(20056) val reqOin: Int = 0, + @ProtoId(20059) val reqBubbleId: Int = 0, + @ProtoId(21001) val reqMss2Identity: Int = 0, + @ProtoId(21002) val reqMss1Service: Int = 0, + @ProtoId(21003) val reqLflag: Int = 0, + @ProtoId(21004) val reqExtFlag: Int = 0, + @ProtoId(21006) val reqBasicSvrFlag: Int = 0, + @ProtoId(21007) val reqBasicCliFlag: Int = 0, + @ProtoId(24101) val reqPengyouRealname: Int = 0, + @ProtoId(24103) val reqPengyouGender: Int = 0, + @ProtoId(24118) val reqPengyouFlag: Int = 0, + @ProtoId(26004) val reqFullBirthday: Int = 0, + @ProtoId(26005) val reqFullAge: Int = 0, + @ProtoId(26010) val reqSimpleUpdateTime: Int = 0, + @ProtoId(26011) val reqMssUpdateTime: Int = 0, + @ProtoId(27022) val reqGroupMemCreditFlag: Int = 0, + @ProtoId(27025) val reqFaceAddonId: Int = 0, + @ProtoId(27026) val reqMusicGene: Int = 0, + @ProtoId(40323) val reqFileShareBit: Int = 0, + @ProtoId(40404) val reqRecommendPrivacyCtrlBit: Int = 0, + @ProtoId(40505) val reqOldFriendChatBit: Int = 0, + @ProtoId(40602) val reqBusinessBit: Int = 0, + @ProtoId(41305) val reqCrmBit: Int = 0, + @ProtoId(41810) val reqForbidFileshareBit: Int = 0, + @ProtoId(42333) val userLoginGuardFace: Int = 0 ) : ProtoBuf } @@ -959,35 +959,35 @@ class Oidb0x5e1 : ProtoBuf { class Oidb0xc90 : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val communityBid: List? = null, - @SerialId(2) val page: Int = 0 + @ProtoId(1) val communityBid: List? = null, + @ProtoId(2) val page: Int = 0 ) : ProtoBuf @Serializable class CommunityWebInfo( - @SerialId(1) val communityInfoItem: List? = null, - @SerialId(2) val page: Int = 0, - @SerialId(3) val end: Int = 0 + @ProtoId(1) val communityInfoItem: List? = null, + @ProtoId(2) val page: Int = 0, + @ProtoId(3) val end: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val communityInfoItem: List? = null, - @SerialId(2) val jumpConcernCommunityUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val communityTitleWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val moreUrlWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val webCommunityInfo: CommunityWebInfo? = null, - @SerialId(6) val jumpCommunityChannelUrl: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val communityInfoItem: List? = null, + @ProtoId(2) val jumpConcernCommunityUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val communityTitleWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val moreUrlWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val webCommunityInfo: CommunityWebInfo? = null, + @ProtoId(6) val jumpCommunityChannelUrl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class CommunityConfigInfo( - @SerialId(1) val jumpHomePageUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val name: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val picUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val dynamicCount: Int = 0, - @SerialId(5) val communityBid: Long = 0L, - @SerialId(6) val followStatus: Int = 0 + @ProtoId(1) val jumpHomePageUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val name: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val picUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val dynamicCount: Int = 0, + @ProtoId(5) val communityBid: Long = 0L, + @ProtoId(6) val followStatus: Int = 0 ) : ProtoBuf } @@ -995,22 +995,22 @@ class Oidb0xc90 : ProtoBuf { class Cmd0xd8a : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val retcode: Int = 0, - @SerialId(2) val res: String = "" + @ProtoId(1) val retcode: Int = 0, + @ProtoId(2) val res: String = "" ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val cmd: Int = 0, - @SerialId(3) val body: String = "", - @SerialId(4) val clientInfo: ClientInfo? = null + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val cmd: Int = 0, + @ProtoId(3) val body: String = "", + @ProtoId(4) val clientInfo: ClientInfo? = null ) : ProtoBuf @Serializable class ClientInfo( - @SerialId(1) val implat: Int = 0, - @SerialId(2) val ingClientver: String = "" + @ProtoId(1) val implat: Int = 0, + @ProtoId(2) val ingClientver: String = "" ) : ProtoBuf } @@ -1018,43 +1018,43 @@ class Cmd0xd8a : ProtoBuf { class Oidb0xb6f : ProtoBuf { @Serializable class ReportFreqRspBody( - @SerialId(1) val identity: Identity? = null, - @SerialId(4) val remainTimes: Long = 0L, - @SerialId(5) val expireTime: Int = 0 + @ProtoId(1) val identity: Identity? = null, + @ProtoId(4) val remainTimes: Long = 0L, + @ProtoId(5) val expireTime: Int = 0 ) : ProtoBuf @Serializable class Identity( - @SerialId(1) val apiName: String = "", - @SerialId(2) val appid: Int = 0, - @SerialId(3) val apptype: Int = 0, - @SerialId(4) val bizid: Int = 0, - @SerialId(10) val intExt1: Long = 0L, - @SerialId(20) val ext1: String = "" + @ProtoId(1) val apiName: String = "", + @ProtoId(2) val appid: Int = 0, + @ProtoId(3) val apptype: Int = 0, + @ProtoId(4) val bizid: Int = 0, + @ProtoId(10) val intExt1: Long = 0L, + @ProtoId(20) val ext1: String = "" ) : ProtoBuf @Serializable class ThresholdInfo( - @SerialId(1) val thresholdPerMinute: Long = 0L, - @SerialId(2) val thresholdPerDay: Long = 0L, - @SerialId(3) val thresholdPerHour: Long = 0L, - @SerialId(4) val thresholdPerWeek: Long = 0L + @ProtoId(1) val thresholdPerMinute: Long = 0L, + @ProtoId(2) val thresholdPerDay: Long = 0L, + @ProtoId(3) val thresholdPerHour: Long = 0L, + @ProtoId(4) val thresholdPerWeek: Long = 0L ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val reportFreqRsp: ReportFreqRspBody? = null + @ProtoId(1) val reportFreqRsp: ReportFreqRspBody? = null ) : ProtoBuf @Serializable class ReportFreqReqBody( - @SerialId(1) val identity: Identity? = null, - @SerialId(2) val invokeTimes: Long = 1L + @ProtoId(1) val identity: Identity? = null, + @ProtoId(2) val invokeTimes: Long = 1L ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val reportFreqReq: ReportFreqReqBody? = null + @ProtoId(1) val reportFreqReq: ReportFreqReqBody? = null ) : ProtoBuf } @@ -1062,17 +1062,17 @@ class Oidb0xb6f : ProtoBuf { class Cmd0x7dc : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val seq: Int = 0, - @SerialId(2) val wording: String = "", - @SerialId(3) val msgAppointInfo: List? = null + @ProtoId(1) val seq: Int = 0, + @ProtoId(2) val wording: String = "", + @ProtoId(3) val msgAppointInfo: List? = null ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val seq: Int = 0, - @SerialId(2) val msgAppointment: AppointDefine.AppointContent? = null, - @SerialId(3) val msgLbsInfo: AppointDefine.LBSInfo? = null, - @SerialId(4) val overwrite: Int = 0 + @ProtoId(1) val seq: Int = 0, + @ProtoId(2) val msgAppointment: AppointDefine.AppointContent? = null, + @ProtoId(3) val msgLbsInfo: AppointDefine.LBSInfo? = null, + @ProtoId(4) val overwrite: Int = 0 ) : ProtoBuf } @@ -1080,27 +1080,27 @@ class Cmd0x7dc : ProtoBuf { class Cmd0x7cd : ProtoBuf { @Serializable class AppointBrife( - @SerialId(1) val msgPublisherInfo: AppointDefine.PublisherInfo? = null, - @SerialId(2) val msgAppointsInfo: AppointDefine.AppointInfo? = null + @ProtoId(1) val msgPublisherInfo: AppointDefine.PublisherInfo? = null, + @ProtoId(2) val msgAppointsInfo: AppointDefine.AppointInfo? = null ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val stamp: Int = 0, - @SerialId(2) val over: Int = 0, - @SerialId(3) val next: Int = 0, - @SerialId(4) val msgAppointsInfo: List? = null + @ProtoId(1) val stamp: Int = 0, + @ProtoId(2) val over: Int = 0, + @ProtoId(3) val next: Int = 0, + @ProtoId(4) val msgAppointsInfo: List? = null ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val stamp: Int = 0, - @SerialId(2) val start: Int = 0, - @SerialId(3) val want: Int = 0, - @SerialId(4) val msgLbsInfo: AppointDefine.LBSInfo? = null, - @SerialId(5) val msgAppointIds: List? = null, - @SerialId(6) val appointOperation: Int = 0, - @SerialId(100) val requestUin: Long = 0L + @ProtoId(1) val stamp: Int = 0, + @ProtoId(2) val start: Int = 0, + @ProtoId(3) val want: Int = 0, + @ProtoId(4) val msgLbsInfo: AppointDefine.LBSInfo? = null, + @ProtoId(5) val msgAppointIds: List? = null, + @ProtoId(6) val appointOperation: Int = 0, + @ProtoId(100) val requestUin: Long = 0L ) : ProtoBuf } @@ -1108,29 +1108,29 @@ class Cmd0x7cd : ProtoBuf { class Oidb0xc0c : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val isTaskCompleted: Int = 0, - @SerialId(2) val taskPoint: Int = 0, - @SerialId(3) val guideWording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val needShowProgress: Int = 0, - @SerialId(5) val originalProgress: Int = 0, - @SerialId(6) val nowProgress: Int = 0, - @SerialId(7) val totalProgress: Int = 0, - @SerialId(8) val needExecTask: Int = 0 + @ProtoId(1) val isTaskCompleted: Int = 0, + @ProtoId(2) val taskPoint: Int = 0, + @ProtoId(3) val guideWording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val needShowProgress: Int = 0, + @ProtoId(5) val originalProgress: Int = 0, + @ProtoId(6) val nowProgress: Int = 0, + @ProtoId(7) val totalProgress: Int = 0, + @ProtoId(8) val needExecTask: Int = 0 ) : ProtoBuf @Serializable class VideoSrcType( - @SerialId(1) val sourceType: Int = 0, - @SerialId(2) val videoFromType: Int = 0 + @ProtoId(1) val sourceType: Int = 0, + @ProtoId(2) val videoFromType: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val taskType: Int = 0, - @SerialId(3) val rowkey: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val feedsId: Long = 0L, - @SerialId(5) val msgVideoFromType: VideoSrcType? = null + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val taskType: Int = 0, + @ProtoId(3) val rowkey: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val feedsId: Long = 0L, + @ProtoId(5) val msgVideoFromType: VideoSrcType? = null ) : ProtoBuf } @@ -1138,54 +1138,54 @@ class Oidb0xc0c : ProtoBuf { class Cmd0x5fb : ProtoBuf { @Serializable class ReqInfo( - @SerialId(3) val time: Int = 0, - @SerialId(4) val subject: Int = 0, - @SerialId(5) val gender: Int = 0, - @SerialId(6) val ageLow: Int = 0, - @SerialId(7) val ageUp: Int = 0, - @SerialId(8) val profession: Int = 0, - @SerialId(9) val cookie: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val msgDestination: AppointDefine.LocaleInfo? = null + @ProtoId(3) val time: Int = 0, + @ProtoId(4) val subject: Int = 0, + @ProtoId(5) val gender: Int = 0, + @ProtoId(6) val ageLow: Int = 0, + @ProtoId(7) val ageUp: Int = 0, + @ProtoId(8) val profession: Int = 0, + @ProtoId(9) val cookie: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(10) val msgDestination: AppointDefine.LocaleInfo? = null ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val msgHead: BusiReqHead? = null, - @SerialId(2) val msgLbsInfo: AppointDefine.LBSInfo? = null, - @SerialId(3) val reqInfo: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val msgHead: BusiReqHead? = null, + @ProtoId(2) val msgLbsInfo: AppointDefine.LBSInfo? = null, + @ProtoId(3) val reqInfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class BusiRespHead( - @SerialId(1) val int32Version: Int = 1, - @SerialId(2) val int32Seq: Int = 0, - @SerialId(3) val int32ReplyCode: Int = 0, - @SerialId(4) val result: String = "" + @ProtoId(1) val int32Version: Int = 1, + @ProtoId(2) val int32Seq: Int = 0, + @ProtoId(3) val int32ReplyCode: Int = 0, + @ProtoId(4) val result: String = "" ) : ProtoBuf @Serializable class UserProfile( - @SerialId(1) val int64Id: Long = 0L, - @SerialId(2) val int32IdType: Int = 0, - @SerialId(3) val url: String = "", - @SerialId(4) val int32PicType: Int = 0, - @SerialId(5) val int32SubPicType: Int = 0, - @SerialId(6) val title: String = "", - @SerialId(7) val content: String = "", - @SerialId(8) val content2: String = "", - @SerialId(9) val picUrl: String = "" + @ProtoId(1) val int64Id: Long = 0L, + @ProtoId(2) val int32IdType: Int = 0, + @ProtoId(3) val url: String = "", + @ProtoId(4) val int32PicType: Int = 0, + @ProtoId(5) val int32SubPicType: Int = 0, + @ProtoId(6) val title: String = "", + @ProtoId(7) val content: String = "", + @ProtoId(8) val content2: String = "", + @ProtoId(9) val picUrl: String = "" ) : ProtoBuf @Serializable class BusiReqHead( - @SerialId(1) val int32Version: Int = 1, - @SerialId(2) val int32Seq: Int = 0 + @ProtoId(1) val int32Version: Int = 1, + @ProtoId(2) val int32Seq: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val msgHead: BusiRespHead? = null, - @SerialId(2) val msgUserList: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val msgHead: BusiRespHead? = null, + @ProtoId(2) val msgUserList: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @@ -1193,44 +1193,44 @@ class Cmd0x5fb : ProtoBuf { class Oidb0xb61 : ProtoBuf { @Serializable class GetAppinfoReq( - @SerialId(1) val appid: Int = 0, - @SerialId(2) val appType: Int = 0, - @SerialId(3) val platform: Int = 0 + @ProtoId(1) val appid: Int = 0, + @ProtoId(2) val appType: Int = 0, + @ProtoId(3) val platform: Int = 0 ) : ProtoBuf @Serializable class GetPkgUrlReq( - @SerialId(1) val appid: Int = 0, - @SerialId(2) val appType: Int = 0, - @SerialId(3) val appVersion: Int = 0, - @SerialId(4) val platform: Int = 0, - @SerialId(5) val sysVersion: String = "", - @SerialId(6) val qqVersion: String = "" + @ProtoId(1) val appid: Int = 0, + @ProtoId(2) val appType: Int = 0, + @ProtoId(3) val appVersion: Int = 0, + @ProtoId(4) val platform: Int = 0, + @ProtoId(5) val sysVersion: String = "", + @ProtoId(6) val qqVersion: String = "" ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val wording: String = "", - @SerialId(2) val nextReqDuration: Int = 0, - @SerialId(10) val getAppinfoRsp: GetAppinfoRsp? = null, - @SerialId(11) val getMqqappUrlRsp: GetPkgUrlRsp? = null + @ProtoId(1) val wording: String = "", + @ProtoId(2) val nextReqDuration: Int = 0, + @ProtoId(10) val getAppinfoRsp: GetAppinfoRsp? = null, + @ProtoId(11) val getMqqappUrlRsp: GetPkgUrlRsp? = null ) : ProtoBuf @Serializable class ReqBody( - @SerialId(10) val getAppinfoReq: GetAppinfoReq? = null, - @SerialId(11) val getMqqappUrlReq: GetPkgUrlReq? = null + @ProtoId(10) val getAppinfoReq: GetAppinfoReq? = null, + @ProtoId(11) val getMqqappUrlReq: GetPkgUrlReq? = null ) : ProtoBuf @Serializable class GetAppinfoRsp( - @SerialId(1) val appinfo: Qqconnect.Appinfo? = null + @ProtoId(1) val appinfo: Qqconnect.Appinfo? = null ) : ProtoBuf @Serializable class GetPkgUrlRsp( - @SerialId(1) val appVersion: Int = 0, - @SerialId(2) val pkgUrl: String = "" + @ProtoId(1) val appVersion: Int = 0, + @ProtoId(2) val pkgUrl: String = "" ) : ProtoBuf } @@ -1238,52 +1238,52 @@ class Oidb0xb61 : ProtoBuf { class Oidb0xb60 : ProtoBuf { @Serializable class GetPrivilegeReq( - @SerialId(1) val appid: Int = 0, - @SerialId(2) val appType: Int = 3 + @ProtoId(1) val appid: Int = 0, + @ProtoId(2) val appType: Int = 3 ) : ProtoBuf @Serializable class CheckUrlReq( - @SerialId(1) val appid: Int = 0, - @SerialId(2) val appType: Int = 0, - @SerialId(3) val url: String = "" + @ProtoId(1) val appid: Int = 0, + @ProtoId(2) val appType: Int = 0, + @ProtoId(3) val url: String = "" ) : ProtoBuf @Serializable class ClientInfo( - @SerialId(1) val platform: Int = 0, - @SerialId(2) val sdkVersion: String = "", - @SerialId(3) val androidPackageName: String = "", - @SerialId(4) val androidSignature: String = "", - @SerialId(5) val iosBundleId: String = "", - @SerialId(6) val pcSign: String = "" + @ProtoId(1) val platform: Int = 0, + @ProtoId(2) val sdkVersion: String = "", + @ProtoId(3) val androidPackageName: String = "", + @ProtoId(4) val androidSignature: String = "", + @ProtoId(5) val iosBundleId: String = "", + @ProtoId(6) val pcSign: String = "" ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val wording: String = "", - @SerialId(10) val getPrivilegeRsp: GetPrivilegeRsp? = null, - @SerialId(11) val checkUrlRsp: CheckUrlRsp? = null + @ProtoId(1) val wording: String = "", + @ProtoId(10) val getPrivilegeRsp: GetPrivilegeRsp? = null, + @ProtoId(11) val checkUrlRsp: CheckUrlRsp? = null ) : ProtoBuf @Serializable class CheckUrlRsp( - @SerialId(1) val isAuthed: Boolean = false, - @SerialId(2) val nextReqDuration: Int = 0 + @ProtoId(1) val isAuthed: Boolean = false, + @ProtoId(2) val nextReqDuration: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val clientInfo: ClientInfo? = null, - @SerialId(10) val getPrivilegeReq: GetPrivilegeReq? = null, - @SerialId(11) val checkUrlReq: CheckUrlReq? = null + @ProtoId(1) val clientInfo: ClientInfo? = null, + @ProtoId(10) val getPrivilegeReq: GetPrivilegeReq? = null, + @ProtoId(11) val checkUrlReq: CheckUrlReq? = null ) : ProtoBuf @Serializable class GetPrivilegeRsp( - @SerialId(1) val apiGroups: List? = null, - @SerialId(2) val nextReqDuration: Int = 0, - @SerialId(3) val apiNames: List = listOf() + @ProtoId(1) val apiGroups: List? = null, + @ProtoId(2) val nextReqDuration: Int = 0, + @ProtoId(3) val apiNames: List = listOf() ) : ProtoBuf } @@ -1291,26 +1291,26 @@ class Oidb0xb60 : ProtoBuf { class Cmd0x5fc : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val lastEventId: Long = 0L, - @SerialId(2) val readEventId: Long = 0L, - @SerialId(3) val fetchCount: Int = 0, - @SerialId(4) val lastNearbyEventId: Long = 0L, - @SerialId(5) val readNearbyEventId: Long = 0L, - @SerialId(6) val fetchNearbyEventCount: Int = 0, - @SerialId(7) val lastFeedEventId: Long = 0L, - @SerialId(8) val readFeedEventId: Long = 0L, - @SerialId(9) val fetchFeedEventCount: Int = 0 + @ProtoId(1) val lastEventId: Long = 0L, + @ProtoId(2) val readEventId: Long = 0L, + @ProtoId(3) val fetchCount: Int = 0, + @ProtoId(4) val lastNearbyEventId: Long = 0L, + @ProtoId(5) val readNearbyEventId: Long = 0L, + @ProtoId(6) val fetchNearbyEventCount: Int = 0, + @ProtoId(7) val lastFeedEventId: Long = 0L, + @ProtoId(8) val readFeedEventId: Long = 0L, + @ProtoId(9) val fetchFeedEventCount: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val msgEventList: List? = null, - @SerialId(2) val actAppointIds: List? = null, - @SerialId(3) val maxEventId: Long = 0L, - @SerialId(4) val errorTips: String = "", - @SerialId(5) val msgNearbyEventList: List? = null, - @SerialId(6) val msgFeedEventList: List? = null, - @SerialId(7) val maxFreshEventId: Long = 0L + @ProtoId(1) val msgEventList: List? = null, + @ProtoId(2) val actAppointIds: List? = null, + @ProtoId(3) val maxEventId: Long = 0L, + @ProtoId(4) val errorTips: String = "", + @ProtoId(5) val msgNearbyEventList: List? = null, + @ProtoId(6) val msgFeedEventList: List? = null, + @ProtoId(7) val maxFreshEventId: Long = 0L ) : ProtoBuf } @@ -1318,9 +1318,9 @@ class Cmd0x5fc : ProtoBuf { class Oidb0xc33 : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val result: Int = 0, - @SerialId(2) val nextGap: Int = 0, - @SerialId(3) val newUser: Int = 0 + @ProtoId(1) val result: Int = 0, + @ProtoId(2) val nextGap: Int = 0, + @ProtoId(3) val newUser: Int = 0 ) : ProtoBuf @Serializable @@ -1331,38 +1331,38 @@ class Oidb0xc33 : ProtoBuf { class Oidb0xc0b : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val isOpenCoinEntry: Int = 0, - @SerialId(2) val canGetCoinCount: Int = 0, - @SerialId(3) val coinIconUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val lastCompletedTaskStamp: Long = 0L, - @SerialId(6) val cmsWording: List? = null, - @SerialId(7) val lastCmsActivityStamp: Long = 0L, - @SerialId(8) val msgKandianCoinRemind: KanDianCoinRemind? = null, - @SerialId(9) val msgKandianTaskRemind: KanDianTaskRemind? = null + @ProtoId(1) val isOpenCoinEntry: Int = 0, + @ProtoId(2) val canGetCoinCount: Int = 0, + @ProtoId(3) val coinIconUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val lastCompletedTaskStamp: Long = 0L, + @ProtoId(6) val cmsWording: List? = null, + @ProtoId(7) val lastCmsActivityStamp: Long = 0L, + @ProtoId(8) val msgKandianCoinRemind: KanDianCoinRemind? = null, + @ProtoId(9) val msgKandianTaskRemind: KanDianTaskRemind? = null ) : ProtoBuf @Serializable class KanDianCoinRemind( - @SerialId(1) val wording: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val wording: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class KanDianTaskRemind( - @SerialId(1) val wording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val taskType: Int = 0 + @ProtoId(1) val wording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val taskType: Int = 0 ) : ProtoBuf @Serializable class KanDianCMSActivityInfo( - @SerialId(1) val activityId: Long = 0L, - @SerialId(2) val wording: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(3) val pictureUrl: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val activityId: Long = 0L, + @ProtoId(2) val wording: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(3) val pictureUrl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L + @ProtoId(1) val uin: Long = 0L ) : ProtoBuf } @@ -1370,26 +1370,26 @@ class Oidb0xc0b : ProtoBuf { class Cmd0xc85 : ProtoBuf { @Serializable class ReqBody( - @SerialId(101) val fromUin: Long = 0L, - @SerialId(102) val toUin: Long = 0L, - @SerialId(103) val op: Int = 0, - @SerialId(104) val intervalDays: Int = 0 + @ProtoId(101) val fromUin: Long = 0L, + @ProtoId(102) val toUin: Long = 0L, + @ProtoId(103) val op: Int = 0, + @ProtoId(104) val intervalDays: Int = 0 ) : ProtoBuf @Serializable class InteractionDetailInfo( - @SerialId(101) val continuousRecordDays: Int = 0, - @SerialId(102) val sendDayTime: Int = 0, - @SerialId(103) val recvDayTime: Int = 0, - @SerialId(104) val sendRecord: String = "", - @SerialId(105) val recvRecord: String = "" + @ProtoId(101) val continuousRecordDays: Int = 0, + @ProtoId(102) val sendDayTime: Int = 0, + @ProtoId(103) val recvDayTime: Int = 0, + @ProtoId(104) val sendRecord: String = "", + @ProtoId(105) val recvRecord: String = "" ) : ProtoBuf @Serializable class RspBody( - @SerialId(101) val result: Int = 0, - @SerialId(102) val recentInteractionTime: Int = 0, - @SerialId(103) val interactionDetailInfo: InteractionDetailInfo? = null + @ProtoId(101) val result: Int = 0, + @ProtoId(102) val recentInteractionTime: Int = 0, + @ProtoId(103) val interactionDetailInfo: InteractionDetailInfo? = null ) : ProtoBuf } @@ -1397,46 +1397,46 @@ class Cmd0xc85 : ProtoBuf { class Cmd0x7ce : ProtoBuf { @Serializable class AppintDetail( - @SerialId(1) val msgPublisherInfo: AppointDefine.PublisherInfo? = null, - @SerialId(2) val msgAppointsInfo: AppointDefine.AppointInfo? = null, - @SerialId(3) val score: Int = 0, - @SerialId(4) val joinOver: Int = 0, - @SerialId(5) val joinNext: Int = 0, - @SerialId(6) val msgStrangerInfo: List? = null, - @SerialId(7) val viewOver: Int = 0, - @SerialId(8) val viewNext: Int = 0, - @SerialId(9) val msgVistorInfo: List? = null, - @SerialId(10) val meJoin: Int = 0, - @SerialId(12) val canProfile: Int = 0, - @SerialId(13) val profileErrmsg: String = "", - @SerialId(14) val canAio: Int = 0, - @SerialId(15) val aioErrmsg: String = "", - @SerialId(16) val sigC2C: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(17) val uin: Long = 0L, - @SerialId(18) val limited: Int = 0, - @SerialId(19) val msgCommentList: List? = null, - @SerialId(20) val commentOver: Int = 0, - @SerialId(23) val meInvited: Int = 0 + @ProtoId(1) val msgPublisherInfo: AppointDefine.PublisherInfo? = null, + @ProtoId(2) val msgAppointsInfo: AppointDefine.AppointInfo? = null, + @ProtoId(3) val score: Int = 0, + @ProtoId(4) val joinOver: Int = 0, + @ProtoId(5) val joinNext: Int = 0, + @ProtoId(6) val msgStrangerInfo: List? = null, + @ProtoId(7) val viewOver: Int = 0, + @ProtoId(8) val viewNext: Int = 0, + @ProtoId(9) val msgVistorInfo: List? = null, + @ProtoId(10) val meJoin: Int = 0, + @ProtoId(12) val canProfile: Int = 0, + @ProtoId(13) val profileErrmsg: String = "", + @ProtoId(14) val canAio: Int = 0, + @ProtoId(15) val aioErrmsg: String = "", + @ProtoId(16) val sigC2C: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(17) val uin: Long = 0L, + @ProtoId(18) val limited: Int = 0, + @ProtoId(19) val msgCommentList: List? = null, + @ProtoId(20) val commentOver: Int = 0, + @ProtoId(23) val meInvited: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val msgAppointsInfo: List? = null, - @SerialId(2) val secureFlag: Int = 0, - @SerialId(3) val secureTips: String = "" + @ProtoId(1) val msgAppointsInfo: List? = null, + @ProtoId(2) val secureFlag: Int = 0, + @ProtoId(3) val secureTips: String = "" ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val appointIds: List? = null, - @SerialId(2) val joinStart: Int = 0, - @SerialId(3) val joinWant: Int = 0, - @SerialId(4) val viewStart: Int = 0, - @SerialId(5) val viewWant: Int = 0, - @SerialId(6) val msgLbsInfo: AppointDefine.LBSInfo? = null, - @SerialId(7) val uint64Uins: List? = null, - @SerialId(8) val viewCommentCount: Int = 0, - @SerialId(100) val requestUin: Long = 0L + @ProtoId(1) val appointIds: List? = null, + @ProtoId(2) val joinStart: Int = 0, + @ProtoId(3) val joinWant: Int = 0, + @ProtoId(4) val viewStart: Int = 0, + @ProtoId(5) val viewWant: Int = 0, + @ProtoId(6) val msgLbsInfo: AppointDefine.LBSInfo? = null, + @ProtoId(7) val uint64Uins: List? = null, + @ProtoId(8) val viewCommentCount: Int = 0, + @ProtoId(100) val requestUin: Long = 0L ) : ProtoBuf } @@ -1444,18 +1444,18 @@ class Cmd0x7ce : ProtoBuf { class Cmd0x7db : ProtoBuf { @Serializable class RspBody( - @SerialId(1) val wording: String = "", - @SerialId(2) val msgAppointInfo: AppointDefine.AppointInfo? = null, - @SerialId(3) val sigC2C: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val appointAction: Int = 0 + @ProtoId(1) val wording: String = "", + @ProtoId(2) val msgAppointInfo: AppointDefine.AppointInfo? = null, + @ProtoId(3) val sigC2C: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val appointAction: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(1) val msgAppointId: AppointDefine.AppointID? = null, - @SerialId(2) val appointAction: Int = 0, - @SerialId(3) val overwrite: Int = 0, - @SerialId(4) val msgAppointIds: List? = null + @ProtoId(1) val msgAppointId: AppointDefine.AppointID? = null, + @ProtoId(2) val appointAction: Int = 0, + @ProtoId(3) val overwrite: Int = 0, + @ProtoId(4) val msgAppointIds: List? = null ) : ProtoBuf } @@ -1463,14 +1463,14 @@ class Cmd0x7db : ProtoBuf { class Oidb0xc6c : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val msgGroupInfo: List? = null + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val msgGroupInfo: List? = null ) : ProtoBuf @Serializable class GroupInfo( - @SerialId(1) val groupUin: Long = 0L, - @SerialId(2) val groupCode: Long = 0L + @ProtoId(1) val groupUin: Long = 0L, + @ProtoId(2) val groupCode: Long = 0L ) : ProtoBuf @Serializable @@ -1481,40 +1481,40 @@ class Oidb0xc6c : ProtoBuf { class Oidb0xc05 : ProtoBuf { @Serializable class GetAuthAppListReq( - @SerialId(1) val start: Int = 0, - @SerialId(2) val limit: Int = 0 + @ProtoId(1) val start: Int = 0, + @ProtoId(2) val limit: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val wording: String = "", - @SerialId(10) val getCreateAppListRsp: GetCreateAppListRsp? = null, - @SerialId(11) val getAuthAppListRsp: GetAuthAppListRsp? = null + @ProtoId(1) val wording: String = "", + @ProtoId(10) val getCreateAppListRsp: GetCreateAppListRsp? = null, + @ProtoId(11) val getAuthAppListRsp: GetAuthAppListRsp? = null ) : ProtoBuf @Serializable class GetCreateAppListRsp( - @SerialId(1) val totalCount: Int = 0, - @SerialId(2) val appinfos: List? = null + @ProtoId(1) val totalCount: Int = 0, + @ProtoId(2) val appinfos: List? = null ) : ProtoBuf @Serializable class GetAuthAppListRsp( - @SerialId(1) val totalCount: Int = 0, - @SerialId(2) val appinfos: List? = null, - @SerialId(3) val curIndex: Int = 0 + @ProtoId(1) val totalCount: Int = 0, + @ProtoId(2) val appinfos: List? = null, + @ProtoId(3) val curIndex: Int = 0 ) : ProtoBuf @Serializable class ReqBody( - @SerialId(10) val getCreateAppListReq: GetCreateAppListReq? = null, - @SerialId(11) val getAuthAppListReq: GetAuthAppListReq? = null + @ProtoId(10) val getCreateAppListReq: GetCreateAppListReq? = null, + @ProtoId(11) val getAuthAppListReq: GetAuthAppListReq? = null ) : ProtoBuf @Serializable class GetCreateAppListReq( - @SerialId(1) val start: Int = 0, - @SerialId(2) val limit: Int = 0 + @ProtoId(1) val start: Int = 0, + @ProtoId(2) val limit: Int = 0 ) : ProtoBuf } @@ -1522,17 +1522,17 @@ class Oidb0xc05 : ProtoBuf { class Cmd0x7da : ProtoBuf { @Serializable class ReqBody( - @SerialId(1) val msgAppointIds: List? = null, - @SerialId(2) val appointOperation: Int = 0, - @SerialId(3) val operationReason: Int = 0, - @SerialId(4) val overwrite: Int = 0 + @ProtoId(1) val msgAppointIds: List? = null, + @ProtoId(2) val appointOperation: Int = 0, + @ProtoId(3) val operationReason: Int = 0, + @ProtoId(4) val overwrite: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val wording: String = "", - @SerialId(2) val msgAppointInfo: List? = null, - @SerialId(3) val operationReason: Int = 0 + @ProtoId(1) val wording: String = "", + @ProtoId(2) val msgAppointInfo: List? = null, + @ProtoId(3) val operationReason: Int = 0 ) : ProtoBuf } @@ -1540,118 +1540,118 @@ class Cmd0x7da : ProtoBuf { class Qqconnect : ProtoBuf { @Serializable class MobileAppInfo( - @SerialId(11) val androidAppInfo: List? = null, - @SerialId(12) val iosAppInfo: List? = null + @ProtoId(11) val androidAppInfo: List? = null, + @ProtoId(12) val iosAppInfo: List? = null ) : ProtoBuf @Serializable class TemplateMsgConfig( - @SerialId(1) val serviceMsgUin: Long = 0L, - @SerialId(2) val publicMsgUin: Long = 0L, - @SerialId(3) val campMsgUin: Long = 0L + @ProtoId(1) val serviceMsgUin: Long = 0L, + @ProtoId(2) val publicMsgUin: Long = 0L, + @ProtoId(3) val campMsgUin: Long = 0L ) : ProtoBuf @Serializable class Appinfo( - @SerialId(1) val appid: Int = 0, - @SerialId(2) val appType: Int = 0, - @SerialId(3) val platform: Int = 0, - @SerialId(4) val appName: String = "", - @SerialId(5) val appKey: String = "", - @SerialId(6) val appState: Int = 0, - @SerialId(7) val iphoneUrlScheme: String = "", - @SerialId(8) val androidPackName: String = "", - @SerialId(9) val iconUrl: String = "", - @SerialId(10) val sourceUrl: String = "", - @SerialId(11) val iconSmallUrl: String = "", - @SerialId(12) val iconMiddleUrl: String = "", - @SerialId(13) val tencentDocsAppinfo: TencentDocsAppinfo? = null, - @SerialId(21) val developerUin: Long = 0L, - @SerialId(22) val appClass: Int = 0, - @SerialId(23) val appSubclass: Int = 0, - @SerialId(24) val remark: String = "", - @SerialId(25) val iconMiniUrl: String = "", - @SerialId(26) val authTime: Long = 0L, - @SerialId(27) val appUrl: String = "", - @SerialId(28) val universalLink: String = "", - @SerialId(29) val qqconnectFeature: Int = 0, - @SerialId(30) val isHatchery: Int = 0, - @SerialId(31) val testUinList: List? = null, - @SerialId(100) val templateMsgConfig: TemplateMsgConfig? = null, - @SerialId(101) val miniAppInfo: MiniAppInfo? = null, - @SerialId(102) val webAppInfo: WebAppInfo? = null, - @SerialId(103) val mobileAppInfo: MobileAppInfo? = null + @ProtoId(1) val appid: Int = 0, + @ProtoId(2) val appType: Int = 0, + @ProtoId(3) val platform: Int = 0, + @ProtoId(4) val appName: String = "", + @ProtoId(5) val appKey: String = "", + @ProtoId(6) val appState: Int = 0, + @ProtoId(7) val iphoneUrlScheme: String = "", + @ProtoId(8) val androidPackName: String = "", + @ProtoId(9) val iconUrl: String = "", + @ProtoId(10) val sourceUrl: String = "", + @ProtoId(11) val iconSmallUrl: String = "", + @ProtoId(12) val iconMiddleUrl: String = "", + @ProtoId(13) val tencentDocsAppinfo: TencentDocsAppinfo? = null, + @ProtoId(21) val developerUin: Long = 0L, + @ProtoId(22) val appClass: Int = 0, + @ProtoId(23) val appSubclass: Int = 0, + @ProtoId(24) val remark: String = "", + @ProtoId(25) val iconMiniUrl: String = "", + @ProtoId(26) val authTime: Long = 0L, + @ProtoId(27) val appUrl: String = "", + @ProtoId(28) val universalLink: String = "", + @ProtoId(29) val qqconnectFeature: Int = 0, + @ProtoId(30) val isHatchery: Int = 0, + @ProtoId(31) val testUinList: List? = null, + @ProtoId(100) val templateMsgConfig: TemplateMsgConfig? = null, + @ProtoId(101) val miniAppInfo: MiniAppInfo? = null, + @ProtoId(102) val webAppInfo: WebAppInfo? = null, + @ProtoId(103) val mobileAppInfo: MobileAppInfo? = null ) : ProtoBuf @Serializable class ConnectClientInfo( - @SerialId(1) val platform: Int = 0, - @SerialId(2) val sdkVersion: String = "", - @SerialId(3) val systemName: String = "", - @SerialId(4) val systemVersion: String = "", - @SerialId(21) val androidPackageName: String = "", - @SerialId(22) val androidSignature: String = "", - @SerialId(31) val iosBundleId: String = "", - @SerialId(32) val iosDeviceId: String = "", - @SerialId(33) val iosAppToken: String = "", - @SerialId(41) val pcSign: String = "" + @ProtoId(1) val platform: Int = 0, + @ProtoId(2) val sdkVersion: String = "", + @ProtoId(3) val systemName: String = "", + @ProtoId(4) val systemVersion: String = "", + @ProtoId(21) val androidPackageName: String = "", + @ProtoId(22) val androidSignature: String = "", + @ProtoId(31) val iosBundleId: String = "", + @ProtoId(32) val iosDeviceId: String = "", + @ProtoId(33) val iosAppToken: String = "", + @ProtoId(41) val pcSign: String = "" ) : ProtoBuf @Serializable class TencentDocsAppinfo( - @SerialId(1) val openTypes: String = "", - @SerialId(2) val opts: String = "", - @SerialId(3) val ejs: String = "", - @SerialId(4) val callbackUrlTest: String = "", - @SerialId(5) val callbackUrl: String = "", - @SerialId(6) val domain: String = "", - @SerialId(7) val userinfoCallback: String = "", - @SerialId(8) val userinfoCallbackTest: String = "" + @ProtoId(1) val openTypes: String = "", + @ProtoId(2) val opts: String = "", + @ProtoId(3) val ejs: String = "", + @ProtoId(4) val callbackUrlTest: String = "", + @ProtoId(5) val callbackUrl: String = "", + @ProtoId(6) val domain: String = "", + @ProtoId(7) val userinfoCallback: String = "", + @ProtoId(8) val userinfoCallbackTest: String = "" ) : ProtoBuf @Serializable class WebAppInfo( - @SerialId(1) val websiteUrl: String = "", - @SerialId(2) val provider: String = "", - @SerialId(3) val icp: String = "", - @SerialId(4) val callbackUrl: String = "" + @ProtoId(1) val websiteUrl: String = "", + @ProtoId(2) val provider: String = "", + @ProtoId(3) val icp: String = "", + @ProtoId(4) val callbackUrl: String = "" ) : ProtoBuf @Serializable class IOSAppInfo( - @SerialId(1) val bundleId: String = "", - @SerialId(2) val urlScheme: String = "", - @SerialId(3) val storeId: String = "" + @ProtoId(1) val bundleId: String = "", + @ProtoId(2) val urlScheme: String = "", + @ProtoId(3) val storeId: String = "" ) : ProtoBuf @Serializable class MsgUinInfo( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val msgType: Int = 0, - @SerialId(3) val appid: Int = 0, - @SerialId(4) val appType: Int = 0, - @SerialId(5) val ctime: Int = 0, - @SerialId(6) val mtime: Int = 0, - @SerialId(7) val mpType: Int = 0, - @SerialId(100) val nick: String = "", - @SerialId(101) val faceUrl: String = "" + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val msgType: Int = 0, + @ProtoId(3) val appid: Int = 0, + @ProtoId(4) val appType: Int = 0, + @ProtoId(5) val ctime: Int = 0, + @ProtoId(6) val mtime: Int = 0, + @ProtoId(7) val mpType: Int = 0, + @ProtoId(100) val nick: String = "", + @ProtoId(101) val faceUrl: String = "" ) : ProtoBuf @Serializable class MiniAppInfo( - @SerialId(1) val superUin: Long = 0L, - @SerialId(11) val ownerType: Int = 0, - @SerialId(12) val ownerName: String = "", - @SerialId(13) val ownerIdCardType: Int = 0, - @SerialId(14) val ownerIdCard: String = "", - @SerialId(15) val ownerStatus: Int = 0 + @ProtoId(1) val superUin: Long = 0L, + @ProtoId(11) val ownerType: Int = 0, + @ProtoId(12) val ownerName: String = "", + @ProtoId(13) val ownerIdCardType: Int = 0, + @ProtoId(14) val ownerIdCard: String = "", + @ProtoId(15) val ownerStatus: Int = 0 ) : ProtoBuf @Serializable class AndroidAppInfo( - @SerialId(1) val packName: String = "", - @SerialId(2) val packSign: String = "", - @SerialId(3) val apkDownUrl: String = "" + @ProtoId(1) val packName: String = "", + @ProtoId(2) val packSign: String = "", + @ProtoId(3) val apkDownUrl: String = "" ) : ProtoBuf } @@ -1659,14 +1659,14 @@ class Qqconnect : ProtoBuf { class Sync : ProtoBuf { @Serializable class SyncAppointmentReq( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val msgAppointment: AppointDefine.AppointContent? = null, - @SerialId(3) val msgGpsInfo: AppointDefine.GPS? = null + @ProtoId(1) val uin: Long = 0L, + @ProtoId(2) val msgAppointment: AppointDefine.AppointContent? = null, + @ProtoId(3) val msgGpsInfo: AppointDefine.GPS? = null ) : ProtoBuf @Serializable class SyncAppointmentRsp( - @SerialId(1) val result: Int = 0 + @ProtoId(1) val result: Int = 0 ) : ProtoBuf } @@ -1674,137 +1674,137 @@ class Sync : ProtoBuf { class Oidb0xc26 : ProtoBuf { @Serializable class RgoupLabel( - @SerialId(1) val name: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val enumType: Int /* enum */ = 1, - @SerialId(3) val textColor: RgroupColor? = null, - @SerialId(4) val edgingColor: RgroupColor? = 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: RgroupColor? = null, + @ProtoId(4) val edgingColor: RgroupColor? = null, + @ProtoId(5) val labelAttr: Int = 0, + @ProtoId(6) val labelType: Int = 0 ) : ProtoBuf @Serializable class AddFriendSource( - @SerialId(1) val source: Int = 0, - @SerialId(2) val subSource: Int = 0 + @ProtoId(1) val source: Int = 0, + @ProtoId(2) val subSource: Int = 0 ) : ProtoBuf @Serializable class Label( - @SerialId(1) val name: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(2) val textColor: Color? = null, - @SerialId(3) val edgingColor: Color? = null, - @SerialId(4) val labelType: Int = 0 + @ProtoId(1) val name: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(2) val textColor: Color? = null, + @ProtoId(3) val edgingColor: Color? = null, + @ProtoId(4) val labelType: Int = 0 ) : ProtoBuf @Serializable class EntryDelay( - @SerialId(1) val emEntry: Int /* enum */ = 1, - @SerialId(2) val delay: Int = 0 + @ProtoId(1) val emEntry: Int /* enum */ = 1, + @ProtoId(2) val delay: Int = 0 ) : ProtoBuf @Serializable class RspBody( - @SerialId(1) val msgPersons: List? = null, - @SerialId(2) val entryInuse: List = listOf(), - @SerialId(3) val entryClose: List = listOf(), - @SerialId(4) val nextGap: Int = 0, - @SerialId(5) val timestamp: Int = 0, - @SerialId(6) val msgUp: Int = 0, - @SerialId(7) val entryDelays: List? = null, - @SerialId(8) val listSwitch: Int = 0, - @SerialId(9) val addPageListSwitch: Int = 0, - @SerialId(10) val emRspDataType: Int /* enum */ = 1, - @SerialId(11) val msgRgroupItems: List? = null, - @SerialId(12) val boolIsNewuser: Boolean = false, - @SerialId(13) val msgTables: List? = null, - @SerialId(14) val cookies: ByteArray = EMPTY_BYTE_ARRAY + @ProtoId(1) val msgPersons: List? = null, + @ProtoId(2) val entryInuse: List = listOf(), + @ProtoId(3) val entryClose: List = listOf(), + @ProtoId(4) val nextGap: Int = 0, + @ProtoId(5) val timestamp: Int = 0, + @ProtoId(6) val msgUp: Int = 0, + @ProtoId(7) val entryDelays: List? = null, + @ProtoId(8) val listSwitch: Int = 0, + @ProtoId(9) val addPageListSwitch: Int = 0, + @ProtoId(10) val emRspDataType: Int /* enum */ = 1, + @ProtoId(11) val msgRgroupItems: List? = null, + @ProtoId(12) val boolIsNewuser: Boolean = false, + @ProtoId(13) val msgTables: List? = null, + @ProtoId(14) val cookies: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable class TabInfo( - @SerialId(1) val tabId: Int = 0, - @SerialId(2) val recommendCount: Int = 0, - @SerialId(3) val tableName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(4) val iconUrlSelect: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val iconUrlUnselect: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val backgroundColorSelect: Color? = null, - @SerialId(7) val backgroundColorUnselect: Color? = null + @ProtoId(1) val tabId: Int = 0, + @ProtoId(2) val recommendCount: Int = 0, + @ProtoId(3) val tableName: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(4) val iconUrlSelect: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(5) val iconUrlUnselect: ByteArray = EMPTY_BYTE_ARRAY, + @ProtoId(6) val backgroundColorSelect: Color? = null, + @ProtoId(7) val backgroundColorUnselect: Color? = null ) : ProtoBuf @Serializable class MayKnowPerson( - @SerialId(1) val uin: Long = 0L, - @SerialId(2) val msgIosSource: AddFriendSource? = null, - @SerialId(3) val msgAndroidSource: AddFriendSource? = null, - @SerialId(4) val reason: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(5) val additive: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(6) val nick: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(7) val remark: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(8) val country: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(9) val province: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(10) val city: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(11) val age: Int = 0, - @SerialId(12) val catelogue: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(13) val alghrithm: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(14) val richbuffer: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(15) val qzone: Int = 0, - @SerialId(16) val gender: Int = 0, - @SerialId(17) val mobileName: ByteArray = EMPTY_BYTE_ARRAY, - @SerialId(18) val token: String = "", - @SerialId(19) val onlineState: Int = 0, - @SerialId(20) val msgLabels: List

OutgoingPacketFactory

.decode(bot: QQAndroidBot, packet: ByteReadPacket): P = packet.decode(bot) +private suspend inline fun

OutgoingPacketFactory

.decode(bot: QQAndroidBot, packet: ByteReadPacket): P = + packet.decode(bot) @JvmName("decode1") -private suspend inline fun

IncomingPacketFactory

.decode(bot: QQAndroidBot, packet: ByteReadPacket, sequenceId: Int): P = +private suspend inline fun

IncomingPacketFactory

.decode( + bot: QQAndroidBot, + packet: ByteReadPacket, + sequenceId: Int +): P = packet.decode(bot, sequenceId) internal val DECRYPTER_16_ZERO = ByteArray(16) @@ -117,7 +122,7 @@ internal val PacketLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet").withS /** * 已知的数据包工厂列表. */ -@UseExperimental(ExperimentalUnsignedTypes::class) +@OptIn(ExperimentalUnsignedTypes::class) internal object KnownPacketFactories { object OutgoingFactories : List> by mutableListOf( WtLogin.Login, @@ -138,7 +143,8 @@ internal object KnownPacketFactories { TroopManagement.GetGroupInfo, TroopManagement.EditGroupNametag, TroopManagement.Kick, - Heartbeat.Alive + Heartbeat.Alive, + PbMessageSvc.PbMsgWithDraw ) object IncomingFactories : List> by mutableListOf( @@ -159,84 +165,99 @@ internal object KnownPacketFactories { ?: IncomingFactories.firstOrNull { it.receivingCommandName == commandName } } - // 00 00 08 70 00 00 00 0A 01 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 F5 45 50 03 B7 F7 99 8F DC CE F6 A2 35 08 04 85 D6 1D 52 90 24 13 86 A2 9D 40 D9 CD BF 4B 08 56 E7 EF 37 E6 E7 BE 09 FD A8 E5 D8 DA 07 90 E5 CB 22 1A 5A 77 88 B5 1A 0B 36 2A 71 59 1C 79 7C B1 84 39 CF 74 31 C5 ED 4D BB 12 F3 BB 10 B2 2E CD 05 82 86 AC 78 68 74 C3 42 9F CF F9 14 0D D4 2B 89 E9 FC 4D 1D 29 F3 08 A0 3A 62 F3 B5 71 D3 A9 06 BD 45 BA 50 D6 2D 68 05 B7 B4 D3 07 D0 01 A4 F4 71 0A 76 34 1F CA 2D 3A 53 6A C5 44 CA 56 C9 FA 0F 81 5E 06 63 45 9B BD FA 81 D3 50 46 74 34 C2 A8 21 9B 1B 25 E6 23 38 E5 87 B4 D2 A7 E7 59 65 03 BB 6D 46 D5 21 66 F6 2F 2D BF A3 77 2A 33 9D E1 35 18 45 CE 22 DB C1 52 BB 87 B9 F0 CB B1 CD 39 81 43 BD C1 E5 2B 81 DE 29 EC 72 2A 56 14 23 67 7B 60 F7 FB C1 7F 9C 18 8E ED 67 35 3E 2F EA D1 EA 24 AD 12 6D 65 B3 27 0C 2B 0A 43 42 3E 0C 81 C5 02 7D E6 A5 2C 79 8D 53 2D 06 F1 EE A5 51 BC 24 A6 F5 F2 0C BC 1F 42 F2 21 8F FB DF 9C 98 6F 25 FB D0 09 E1 F3 85 76 A2 96 39 5E 73 06 24 F8 1E 6C F7 D3 03 15 44 BA 6B 42 9E CE 14 7B 1A F9 0B A1 9A 85 FD 52 2B 34 FB 0F D8 6F 34 38 6E F3 A2 7D EB DF 3F 55 6A 52 27 35 44 5C 26 05 F3 36 8E 8F 00 D2 31 85 BF 20 8A 1C 37 57 C4 74 36 BF 89 3F 44 3C 97 AA 6B 8F D1 B6 CD D6 45 88 F1 74 35 CD 8B 85 9F 60 F4 B7 00 3E 87 10 C4 3F 7C 3F 22 F0 20 58 40 CE E6 77 8B 3D EE AA EB 31 52 65 BE C4 12 2B 28 65 1A 26 16 55 E9 86 28 90 42 FD 48 D6 6F E6 63 E2 40 3F BE 68 C6 FE A7 B2 10 31 15 7E B4 C7 2C 8D D9 04 8D AD 8E 45 D2 CA EC 07 D6 D0 14 FD 93 02 4B 3D 71 6E 3E 1F D5 BC ED 98 0E 01 6B FE 1C 96 24 34 DF A3 E3 EE 67 40 F3 C7 AA 10 4E 44 62 49 D5 45 14 15 49 04 2F 9F FC 5D 40 F7 5A CC C1 78 7B 37 35 80 F9 10 80 EB 40 DB 33 06 5F 2A 70 88 F0 94 49 59 48 4B 00 66 28 E9 E9 E7 CE E4 CA 6D 21 79 09 62 7A 6E C5 47 A4 08 85 73 43 35 2D EE E4 0E 85 47 92 B4 F1 5C 76 C2 72 BC D1 70 41 6D 9B EE F4 47 67 1F 63 57 B9 65 C1 08 5C 42 D6 28 FB 6F F5 1A 4B E0 B7 5A 36 75 E6 BE ED C6 67 DA 64 19 09 11 CF 9A 74 4A F4 4E 4F 1E 02 2C 8C 21 21 DE 76 15 34 E5 F2 DF F9 34 31 99 4F 4B B2 5E E6 2B 8D 7C 64 E9 7D 78 98 4F 28 FF 2E 95 1B 5D 8E 2F 70 E6 01 0A 82 18 90 16 8F 98 5E 0F CA 30 4F 36 AF 9B 5E 2B 5B FE 59 5E 11 BC A5 68 64 55 BD 04 CE 43 60 4C C9 CB F7 88 D2 30 7A 89 75 3D 93 9D A9 D0 CE 7D 4B CE 4D 14 5E 95 0E 18 3E 75 AE 20 C6 8A 6E 15 F9 39 58 3B 17 74 99 98 07 5D 68 B1 51 57 AF 1D 85 F1 A5 8A FE 9F 07 BC CB B9 84 A5 49 AE 9F F4 50 06 27 24 7F EB 66 C0 F7 C4 4F E9 74 FA 69 8E D0 A6 A4 3E 8A A8 D9 10 A1 05 03 0F B1 E7 80 22 A9 21 22 FA 31 A1 61 0E 5A 42 75 5F 19 95 2E FE A9 38 51 BB E9 C1 6B 29 CD AE 8F 78 7C 1F A4 81 F2 20 D3 15 C1 EA 42 BD 5F 52 C1 BA 5F C7 42 97 A8 F1 FA C6 20 82 3C 00 36 96 75 69 27 FE 93 AA BD 43 BD 0E FD ED 30 97 A6 B2 A2 E8 D3 75 C2 BF 35 EC 78 8A 3E 23 9D 6D DD 3C D4 4A BC 5A A8 74 86 3C A7 31 F5 F3 8A 78 9C 3C 75 88 B1 35 4A 83 91 EB 5F 80 F3 E0 0C EC 82 5C 7B 08 61 45 E3 3B 7B F1 1F BC 6D 5E 99 3A 34 68 28 20 42 8C 42 C9 0A 3F 32 42 03 E5 9A 71 BB E1 02 C9 B4 98 6E 73 7E EC 81 79 28 01 A8 E5 AC 0F 24 0A 58 DA C1 D2 F0 2C 94 E7 4D E5 ED B3 43 58 23 73 1D 7F 33 B0 7B 1A A8 C1 AC 6E 7C F1 A6 51 0F CF E9 51 5F 06 EB 79 C4 0C E9 78 6C B8 B8 11 C0 2E 84 BC F4 4D 05 D5 B5 90 20 30 BE 73 28 83 91 3B 4D 90 2E 8F A5 A8 BF 85 63 29 05 04 33 14 7D 24 24 7D 02 9B 39 5D 30 D0 68 58 57 1A 0D 67 A6 EF 44 4C 4E 92 14 0A 2A E4 24 1E 60 78 A3 65 71 7E E3 B2 44 E1 55 3E F2 08 05 F5 59 5E EE D3 0A 12 34 C2 FC 74 4A 8C F9 BE 7F 4D EC 9E D3 14 97 7F DF 75 E2 52 21 95 65 99 F4 1D 01 AD F4 D2 4D 8C 9E 5A 16 DE 65 AA D0 AC 24 17 FE BE B9 D8 9B FB 36 33 E5 AE 1B F9 B1 AE A4 9B 15 94 91 6D 45 BE 61 33 4B 71 40 BC DB D0 BF 06 39 83 D5 5F 30 6D E0 20 D3 33 AA 78 13 DA 9A 80 A3 38 4F 3B F8 AE B9 7F 9B B7 F1 AD 10 88 96 DD 79 DD F8 86 A3 72 A2 3F C2 6E B2 6D CE 36 54 90 5F DE 23 90 CD BD BD DB 95 83 5A 94 8E B8 54 AF 0A AB 10 0C 15 B3 3D 2B 89 7D 83 64 B9 29 08 50 40 6E F6 EC 8D E7 79 6C 2B 70 A5 07 FA AD 6A A1 54 EC D7 C7 70 A6 9F 8F 8E 5B 00 5E 8D 27 0D 21 8B 38 31 02 88 13 6E F4 5C 8D 15 82 FC 3E AE 57 17 3D 6D 38 F9 8C 2B EF 87 FA 30 FB 59 D4 E2 49 CF 76 75 9C 9D 50 8D 4A CD AE F6 3C 59 E8 92 BB C1 E2 5C F6 96 8B DC 78 DF E1 27 6B F1 1C D9 68 1D 29 8A E3 09 DD 28 C7 0A ED BB 75 6B D9 87 9F 7F 71 5D D0 EE 03 5B 28 52 40 26 DD C9 C4 52 4D 58 02 3A 71 BE 6C F4 6A 30 14 52 72 CB 45 95 80 DE 28 02 8A 22 7A 05 B5 D7 22 61 06 E0 34 09 AC A9 A8 A8 D0 DF 37 BE 3F 86 33 CA B0 EA 27 A5 95 A5 F8 1C A9 C5 46 41 EB DF C8 5F AD 85 7A 0E A8 AD A9 2A BF F3 6F 39 0D AE 60 30 B2 23 3C D0 37 85 18 01 2E 4E 74 0B 5D 98 2E 1E 3B 3F 30 DC CB DA 1E 0A 4F 0B 70 74 54 28 4F 82 6C 11 7E 5C 7E 33 C7 BB 47 29 2B F6 92 01 3F 83 AE 50 CC 6E B5 87 3B D4 F6 3A 6D 8D 0D 98 37 AB A6 08 79 EE E9 AB 38 37 FA 22 4C 54 80 1F 88 E9 E9 45 37 7A AF DB 61 A9 37 61 30 05 38 46 E4 CD ED B4 71 B2 C7 2E 1F 4C 92 27 C5 AF 3C D9 26 60 1F 4E 65 5E EF F7 A7 9C CC 49 F5 20 58 60 47 ED 44 DF EF FF AD 91 24 28 28 A4 03 F3 10 1B E5 18 0D 49 FA 68 A3 F2 BA 77 32 E4 16 35 66 8F 2C 8E 04 19 5A 26 AE 8C EB D1 C4 6F 0E 38 85 B7 3C 79 F4 98 29 35 FF DF 13 03 FE 36 D7 0A 20 3D 28 14 DE A1 D4 12 5B 6B D0 BF 53 4C AB 36 4E 3A FB 91 73 9C 39 F0 AB 10 1A 25 D6 D6 AC 2F 68 E5 33 67 23 EF 7A 97 70 2C F7 0B 67 0F A6 11 CB AB EF A9 3F 67 5B 8D 1B 39 C9 99 56 79 92 44 6E 1B 6F EE FE E9 08 9C 8B D6 5D 5A 8E 7B BD 61 A4 97 B2 78 75 98 5D C3 19 79 28 26 8B 00 32 D3 E1 3A 02 7F 12 D1 02 2C 5D 27 B3 F0 A6 8B 9D B1 01 01 EC AB 0F 6D D5 E4 0B B0 BD 54 9C 73 CD C0 AB F8 7E D4 D4 D3 3C E9 05 B3 30 30 3D C6 A4 70 EA 0B D1 47 CB 71 41 27 09 DA FE 4A 0F D2 1C 28 EF A6 3E 7A F6 05 A0 52 72 A3 C0 05 BC 8E 38 69 C5 16 AD 98 9D 96 4F FB 95 1D BD B7 12 26 AB 7D E2 EA 57 2C BB 1C 12 C9 FF 90 FB 6B 4F 3D FE 26 7D 4B FF A6 8D 4C D7 FA 4E ED B1 5F 5B 92 92 68 B5 25 0C BC 56 C7 AE 01 C4 05 9A A6 7E 97 93 4E 15 67 83 40 F7 45 8C F8 FB D8 C4 5A D0 94 15 AF 33 24 4A 4E 43 A6 B7 D4 AC 0C EB 39 C9 83 D3 E1 CE 36 6B AC CE FD 3C 92 2D 5D C2 6A 1C C4 54 21 4E 06 8B BB CB 7D B4 C2 FF 53 00 3C A4 47 98 AC 7E 29 AF B2 AB EB 25 1A 7E 3C E0 C6 DF 65 13 64 C1 94 EA 86 3F CD 54 E1 5E C5 95 3D F8 C0 A5 6B 28 CA 4F B4 5A E6 93 CE 49 5E AD CD A1 6E B8 94 9D C4 C3 0E 37 C4 10 A8 C0 47 17 C4 EE 3D 61 B5 80 ED EF 5B CC B4 49 69 10 4D E9 CD 22 B3 1B 1C 52 29 9D 6D B2 84 76 C3 CE 3C 23 39 0D 68 02 6B 1F 3F 28 DA 59 78 AE EA D8 F4 E0 1B 90 21 81 77 23 52 05 53 A1 BD E7 50 1C 24 26 9F 1D 39 E4 F2 A0 F8 7E F7 29 58 41 98 12 62 1A 23 B3 D9 A4 5C D3 15 0D 04 31 48 03 1B CC 5F A0 D1 A9 75 5C D0 FD 8A 9C FA 24 89 B2 C3 A9 C2 13 5B CD 1C F9 B1 63 C7 01 D7 BD 0D 43 2F CB 6C 4B 4F 0D - // 00 00 08 E0 00 00 00 0A 01 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 B4 16 A6 D7 A3 E4 9E 53 99 CD 77 14 70 1F 51 3E 8B 79 F6 93 2B E0 92 E4 32 E2 87 6C 3A 9C 1B 29 87 CB 3C 60 45 9C 41 71 63 6A F6 99 FC 05 01 68 86 B3 6F 37 97 52 C5 D3 0E 66 B3 F6 40 CC EB 18 A3 AE 15 3E 31 B1 E9 7C 6F EC E4 4D 31 F1 1E 2C 0C 1C 45 66 CD F7 1B 90 11 9A D8 CE DD 6D 6C 63 9F EB CD 69 33 AF 6C 8E BA 8C CB C3 FF 27 A2 A6 C3 28 06 4A B5 79 79 12 AB 52 04 62 CA 7D 11 59 85 5C 0B D6 8D 2A E7 9C 04 97 62 7D 05 11 3E 2C 11 60 E3 E3 B3 DA 7A 7C 13 AF 22 01 53 80 69 D0 F9 C8 86 EC 25 8C F3 67 5C 82 45 08 FB 34 43 50 01 0E EA 43 77 D8 CF EC 55 E6 4E 66 5B 26 21 C9 E8 78 92 AE 5C 61 F0 5E 0B E7 34 1F 53 D6 EA 28 9C 02 1A E9 F0 55 61 4B 06 F8 56 3B AC 93 B2 2C CD 66 0D D1 18 CB BD 29 50 DE 0F 82 6D 28 63 AB 21 E1 6C BA B1 9F 69 A4 E3 C9 20 F8 11 82 39 04 2B 54 44 50 FA 2E 86 68 6D DC 5D 9E 18 F4 DD 19 09 BC CF E8 41 68 A3 8D 86 42 80 51 C4 C1 ED 54 DB 50 F5 1D A7 28 2A 0D E8 14 1A 4E F7 96 29 00 6C 9D 4A 2E 3E 7B 4C AC 20 78 F1 3C 70 6B 61 96 D7 EC 77 AD CB AD AF BB 47 C3 1F A0 6C 6C 9C 9F F3 6C EB 6C A4 D0 7F 2B E1 AA 68 26 99 B9 C8 A1 F5 C4 7E E7 E7 81 EE 66 00 96 33 49 C0 EE A2 F9 F6 52 C5 A6 5D EE 9D C5 E5 CE DA 31 FC FF 4B 02 97 68 3D 6A 99 4A CF 69 D9 F4 53 68 31 E7 32 2F 85 E7 7F 16 82 AE FA 73 D5 42 09 9C CB 53 26 79 41 63 80 B0 E2 6A 8B B9 C6 71 08 B4 2B E0 48 D3 C4 0F B0 00 D0 FA 8C 29 DE E9 71 6A D7 89 76 E7 5D 33 14 10 6F E2 44 6A A0 DC C1 CB F3 9A C3 13 CB D1 82 2C DF 34 68 79 E3 09 BD CC 2B 25 79 A8 E7 BE 29 6C 97 C3 D7 F4 0E CC 2B 74 71 02 BA 2B 5B 57 1B C2 C8 C2 BF 54 23 72 EA E4 38 54 20 7D 88 E4 39 7C C5 8A 1B C0 EC D2 1E 7D 1B 6B 7A BC EC 73 1E 53 4A 6F 4F EA F0 56 12 80 BD 0B 37 67 BD FD A8 29 23 2D 8E 66 7E 31 A9 F6 CE 7E BC 4F 38 D0 33 D4 C7 4A E9 43 9D 28 2E 8F 7C D5 81 F4 8C F9 6F 21 AC A1 08 FD F4 01 FB E8 CE 61 91 BE 68 5B E4 3A 5F F8 FB DA 5D 9B 2A AF E2 0C D3 A4 1F 42 90 96 E1 28 44 85 8D E1 CF 19 A9 47 04 8D 28 D9 B3 35 79 48 70 D9 ED 45 B6 24 B5 56 FA 1E DE 02 F3 EB 69 08 7D 24 9C 60 35 97 8D 13 4A 5A 57 BA B3 14 C1 EE 70 22 CA B2 65 F7 BB 3F A2 D9 14 AA 4C 52 E6 E4 10 D3 FD C6 2B DD BF C0 CF E5 35 57 9E 9F D0 77 C8 E6 EF 2B 8E 01 88 96 F8 68 95 A7 0D 58 81 30 60 88 44 CC 31 5B C1 D4 92 6E ED 17 CA 0A 01 69 90 4E 6A C0 D7 09 6C E5 33 64 CA 6E 5C 07 C3 AD 46 36 F9 DF DE B7 71 B2 87 CB 3D 76 C0 44 B8 6B 15 27 B2 03 99 C7 51 8A 00 35 C9 1C 76 55 32 AE 49 5A 34 6A 4E FD 20 7A 24 BF 34 E8 B4 18 BC 92 64 A1 F3 0A 2E 7B 00 EA B6 52 E7 AC 34 FD AE FF 1E 5D 6D D6 1F 6D 06 31 09 9D A9 9C 86 DB 5E 05 07 BA 4A 49 2B D2 7F EE 88 64 B2 6F 15 70 39 1B E9 57 6A 4E 29 4A A4 57 EA 80 3D 86 4C E9 F7 F5 2B C4 9F 35 62 76 09 0E 1C A4 99 50 99 82 2F 84 90 0E 9E 9F 75 C3 15 B0 61 34 D1 67 2D 30 16 FE D3 BF 59 6A B1 74 02 C4 EF 92 85 E0 16 4B 0C C5 9D 65 BB 5D 52 8F 52 5B 7C 7B 74 D9 EC 41 A9 5B FA 2D 95 D4 AE 5D F1 68 88 F6 82 ED 09 05 21 2E 5D 93 64 A0 96 15 64 A6 50 3C 03 2B FC 3E 80 89 90 62 CC D9 23 8E D7 BD 05 02 30 86 32 31 6A 5F F8 C4 BD 61 D0 CE B9 54 4E 93 E9 AE B9 4F 2B 98 DC 23 31 CC A8 06 89 A8 08 60 99 DC D4 81 98 13 C9 27 36 32 24 C1 B0 6B F0 3D EB CC 3B 32 5F 20 72 23 B3 DF 0B 48 3C 35 FD F1 FB DC 3E 2A BE B9 0F 42 56 F1 39 94 86 85 C6 1E A0 4C EC B8 69 45 5F 3D AB 3C 3B A2 70 61 91 9D 2C DD 6D C5 E9 EF 47 36 A6 A3 E0 96 C2 B8 EF 92 E9 E0 26 88 C6 B5 51 BA DE FD C5 BA 4C 6A 9A FE 6F DE B8 10 05 7F 9C 5D 40 11 39 75 CD 36 4F 6B A8 A1 94 57 5F 8F F2 D0 E2 36 A0 A4 24 05 FD 9E F5 51 93 C9 6E 5A 10 8D C3 33 2D E5 09 7A E0 DB 44 63 9C EA A5 ED BF 0B 98 32 F1 BA 04 96 F6 14 49 F1 F8 58 EA 6E 5E 5E 49 CA 2D E2 93 E6 AD 20 B2 CD 98 A7 3E BA 3E A8 - - /** * full packet without length */ // do not inline. Exceptions thrown will not be reported correctly - @UseExperimental(MiraiInternalAPI::class) + @OptIn(MiraiInternalAPI::class) @Suppress("UNCHECKED_CAST") - suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer) = with(rawInput) { - // login - val flag1 = readInt() + suspend fun parseIncomingPacket(bot: QQAndroidBot, rawInput: Input, consumer: PacketConsumer) = + with(rawInput) { + // login + val flag1 = readInt() - PacketLogger.verbose { "开始处理一个包" } - PacketLogger.verbose { "flag1(0A/0B) = ${flag1.toUByte().toUHexString()}" } + PacketLogger.verbose { "开始处理一个包" } + PacketLogger.verbose { "flag1(0A/0B) = ${flag1.toUByte().toUHexString()}" } - val flag2 = readByte().toInt() - PacketLogger.verbose { - "包类型(flag2) = $flag2. (可能是 ${when (flag2) { - 2 -> "OicqRequest" - 1 -> "Uni/ProtoBuf" - 0 -> "Heartbeat" - else -> "未知" - }})" - } + val flag2 = readByte().toInt() + PacketLogger.verbose { + "包类型(flag2) = $flag2. (可能是 ${when (flag2) { + 2 -> "OicqRequest" + 1 -> "Uni/ProtoBuf" + 0 -> "Heartbeat" + else -> "未知" + }})" + } - val flag3 = readByte().toInt() - check(flag3 == 0) { "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. Remaining=${this.readBytes().toUHexString()}" } + val flag3 = readByte().toInt() + check(flag3 == 0) { + "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. Remaining=${this.readBytes() + .toUHexString()}" + } - readString(readInt() - 4)// uinAccount + readString(readInt() - 4)// uinAccount - ByteArrayPool.useInstance { data -> - val size = this.readAvailable(data) + ByteArrayPool.useInstance { data -> + val size = this.readAvailable(data) - kotlin.runCatching { - when (flag2) { - 2 -> data.decryptBy(DECRYPTER_16_ZERO, size).also { PacketLogger.verbose { "成功使用 16 zero 解密" } } - 1 -> data.decryptBy(bot.client.wLoginSigInfo.d2Key, size).also { PacketLogger.verbose { "成功使用 d2Key 解密" } } - 0 -> data - else -> error("") + kotlin.runCatching { + when (flag2) { + 2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size) + .also { PacketLogger.verbose { "成功使用 16 zero 解密" } } + 1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size) + .also { PacketLogger.verbose { "成功使用 d2Key 解密" } } + 0 -> data + else -> error("") + } + }.getOrElse { + PacketLogger.verbose { "失败, 尝试其他各种key" } + bot.client.tryDecryptOrNull(data, size) { it } + }?.toReadPacket()?.let { decryptedData -> + when (flag1) { + 0x0A -> parseSsoFrame(bot, decryptedData) + 0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样. + else -> error("unknown flag1: ${flag1.toByte().toUHexString()}") + } + }?.let { + it as IncomingPacket + + if (it.packetFactory is IncomingPacketFactory && bot.network.pendingEnabled) { + bot.network.pendingIncomingPackets?.addLast(it.also { + it.consumer = consumer + it.flag2 = flag2 + PacketLogger.info { "Cached ${it.commandName} #${it.sequenceId}" } + }) ?: handleIncomingPacket(it, bot, flag2, consumer) + } else { + handleIncomingPacket(it, bot, flag2, consumer) + } + } ?: inline { + PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" } + return } - }.getOrElse { - PacketLogger.verbose { "失败, 尝试其他各种key" } - bot.client.tryDecryptOrNull(data, size) { it } - }?.toReadPacket()?.let { decryptedData -> - when (flag1) { - 0x0A -> parseSsoFrame(bot, decryptedData) - 0x0B -> parseSsoFrame(bot, decryptedData) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样. - else -> error("unknown flag1: ${flag1.toByte().toUHexString()}") - } - }?.let { - it as IncomingPacket - - if (it.packetFactory is IncomingPacketFactory && bot.network.pendingEnabled) { - bot.network.pendingIncomingPackets?.addLast(it.also { - it.consumer = consumer - it.flag2 = flag2 - }) ?: handleIncomingPacket(it, bot, flag2, consumer) - } else { - handleIncomingPacket(it, bot, flag2, consumer) - } - } ?: inline { - PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" } - return } } - } - @UseExperimental(MiraiInternalAPI::class) - internal suspend fun handleIncomingPacket(it: IncomingPacket, bot: QQAndroidBot, flag2: Int, consumer: PacketConsumer) { + @OptIn(MiraiInternalAPI::class) + internal suspend fun handleIncomingPacket( + it: IncomingPacket, + bot: QQAndroidBot, + flag2: Int, + consumer: PacketConsumer + ) { if (it.packetFactory == null) { bot.network.logger.debug("Received commandName: ${it.commandName}") PacketLogger.warning { "找不到 PacketFactory" } - PacketLogger.verbose { "传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> data.toUHexString(length = length) }}" } + PacketLogger.verbose { + "传递给 PacketFactory 的数据 = ${it.data.useBytes { data, length -> + data.toUHexString( + length = length + ) + }}" + } return } + PacketLogger.info { "Handle packet: ${it.commandName}" } it.data.withUse { when (flag2) { 0, 1 -> @@ -255,15 +276,22 @@ internal object KnownPacketFactories { ) } - 2 -> it.data.parseOicqResponse(bot, it.packetFactory as OutgoingPacketFactory, it.sequenceId, consumer) - else -> error("unknown flag2: $flag2. Body to be parsed for inner packet=${it.data.readBytes().toUHexString()}") + 2 -> it.data.parseOicqResponse( + bot, + it.packetFactory as OutgoingPacketFactory, + it.sequenceId, + consumer + ) + else -> error( + "unknown flag2: $flag2. Body to be parsed for inner packet=${it.data.readBytes().toUHexString()}" + ) } } } private inline fun inline(block: () -> R): R = block() - class IncomingPacket( + class IncomingPacket( val packetFactory: PacketFactory?, val sequenceId: Int, val data: ByteReadPacket, @@ -276,12 +304,12 @@ internal object KnownPacketFactories { /** * 解析 SSO 层包装 */ - @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) + @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) private fun parseSsoFrame(bot: QQAndroidBot, input: ByteReadPacket): IncomingPacket<*> { val commandName: String val ssoSequenceId: Int val dataCompressed: Int - input.readPacket(input.readInt() - 4).withUse { + input.readPacketExact(input.readInt() - 4).withUse { ssoSequenceId = readInt() PacketLogger.verbose { "sequenceId = $ssoSequenceId" } val returnCode = readInt() @@ -314,7 +342,7 @@ internal object KnownPacketFactories { 1 -> { input.discardExact(4) input.useBytes { data, length -> - data.unzip(length = length).let { + MiraiPlatformUtils.unzip(data, 0, length).let { val size = it.toInt() if (size == it.size || size == it.size + 4) { it.toReadPacket(offset = 4) @@ -337,12 +365,14 @@ internal object KnownPacketFactories { return IncomingPacket(packetFactory, ssoSequenceId, packet, commandName) } - private suspend fun ByteReadPacket.parseOicqResponse( + @OptIn(MiraiInternalAPI::class) + private suspend fun ByteReadPacket.parseOicqResponse( bot: QQAndroidBot, packetFactory: OutgoingPacketFactory, ssoSequenceId: Int, consumer: PacketConsumer ) { + @Suppress("DuplicatedCode") check(readByte().toInt() == 2) this.discardExact(2) // 27 + 2 + body.size this.discardExact(2) // const, =8001 @@ -354,10 +384,11 @@ internal object KnownPacketFactories { this.discardExact(1) // const = 0 val packet = when (encryptionMethod) { 4 -> { - var data = this.decryptBy(bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt()) + var data = TEA.decrypt(this, bot.client.ecdh.keyPair.initialShareKey, (this.remaining - 1).toInt()) - val peerShareKey = bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey()) - data = data.decryptBy(peerShareKey) + val peerShareKey = + bot.client.ecdh.calculateShareKeyByPeerPublicKey(readUShortLVByteArray().adjustToPublicKey()) + data = TEA.decrypt(data, peerShareKey) packetFactory.decode(bot, data) } @@ -368,13 +399,13 @@ internal object KnownPacketFactories { this.readFully(byteArrayBuffer, 0, size) runCatching { - byteArrayBuffer.decryptBy(bot.client.ecdh.keyPair.initialShareKey, size) + TEA.decrypt(byteArrayBuffer, bot.client.ecdh.keyPair.initialShareKey, size) }.getOrElse { - byteArrayBuffer.decryptBy(bot.client.randomKey, size) + TEA.decrypt(byteArrayBuffer, bot.client.randomKey, size) }.toReadPacket() } } else { - this.decryptBy(bot.client.randomKey, 0, (this.remaining - 1).toInt()) + TEA.decrypt(this, bot.client.randomKey, 0, (this.remaining - 1).toInt()) } packetFactory.decode(bot, data) @@ -386,28 +417,4 @@ internal object KnownPacketFactories { consumer(packetFactory, packet, packetFactory.commandName, ssoSequenceId) } -} - -@UseExperimental(ExperimentalContracts::class) -internal inline fun IoBuffer.withUse(block: IoBuffer.() -> R): R { - contract { - callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE) - } - return try { - block(this) - } finally { - this.release(IoBuffer.Pool) - } -} - -@UseExperimental(ExperimentalContracts::class) -internal inline fun ByteReadPacket.withUse(block: ByteReadPacket.() -> R): R { - contract { - callsInPlace(block, kotlin.contracts.InvocationKind.EXACTLY_ONCE) - } - return try { - block(this) - } finally { - this.close() - } } \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt index 7f5d44ac1..e527fe5ce 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/Tlv.kt @@ -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 } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt new file mode 100644 index 000000000..71e593cac --- /dev/null +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/PbMessageSvc.kt @@ -0,0 +1,125 @@ +/* + * 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.packet.chat + +import kotlinx.io.core.ByteReadPacket +import net.mamoe.mirai.data.Packet +import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf +import net.mamoe.mirai.qqandroid.io.serialization.toByteArray +import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf +import net.mamoe.mirai.qqandroid.network.QQAndroidClient +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgRevokeUserDef +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgSvc +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket +import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory +import net.mamoe.mirai.qqandroid.network.protocol.packet.buildOutgoingUniPacket + +internal class PbMessageSvc { + object PbMsgWithDraw : OutgoingPacketFactory( + "PbMessageSvc.PbMsgWithDraw" + ) { + sealed class Response : Packet { + object Success : Response() { + override fun toString(): String { + return "PbMessageSvc.PbMsgWithDraw.Response.Success" + } + } + + data class Failed( + val result: Int, + val errorMessage: String + ) : Response() + } + + // 12 1A 08 01 10 00 18 E7 C1 AD B8 02 22 0A 08 BF BA 03 10 BF 81 CB B7 03 2A 02 08 00 + fun Group( + client: QQAndroidClient, + groupCode: Long, + messageSequenceId: Int, // 56639 + messageRandom: Int, // 921878719 + messageType: Int = 0 + ): OutgoingPacket = buildOutgoingUniPacket(client) { + writeProtoBuf( + MsgSvc.PbMsgWithDrawReq.serializer(), + MsgSvc.PbMsgWithDrawReq( + groupWithDraw = listOf( + MsgSvc.PbGroupMsgWithDrawReq( + subCmd = 1, + groupType = 0, // 普通群 + groupCode = groupCode, + msgList = listOf( + MsgSvc.PbGroupMsgWithDrawReq.MessageInfo( + msgSeq = messageSequenceId, + msgRandom = messageRandom, + msgType = messageType + ) + ), + userdef = MsgRevokeUserDef.MsgInfoUserDef( + longMessageFlag = 0 + ).toByteArray(MsgRevokeUserDef.MsgInfoUserDef.serializer()) + ) + ) + ) + ) + } + + fun Friend( + client: QQAndroidClient, + toUin: Long, + messageSequenceId: Int, // 56639 + messageRandom: Int, // 921878719 + time: Long + ): OutgoingPacket = buildOutgoingUniPacket(client) { + val messageUid: Long = 262144L.shl(32) or messageRandom.toLong().and(0xffFFffFF) + writeProtoBuf( + MsgSvc.PbMsgWithDrawReq.serializer(), + MsgSvc.PbMsgWithDrawReq( + c2cWithDraw = listOf( + MsgSvc.PbC2CMsgWithDrawReq( + subCmd = 1, + msgInfo = listOf( + MsgSvc.PbC2CMsgWithDrawReq.MsgInfo( + fromUin = client.bot.uin, + toUin = toUin, + msgSeq = messageSequenceId, + msgUid = messageUid, + msgTime = time and 0xffffffff, + routingHead = MsgSvc.RoutingHead( + c2c = MsgSvc.C2C( + toUin = toUin + ) + ) + ) + ) + ) + ) + ) + ) + } + + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { + val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer()) + resp.groupWithDraw?.firstOrNull()?.let { + if (it.result != 0) { + return Response.Failed(it.result, it.errmsg) + } + return Response.Success + } + resp.c2cWithDraw?.firstOrNull()?.let { + if (it.result != 0) { + return Response.Failed(it.result, it.errmsg) + } + return Response.Success + } + return Response.Failed(-1, "No response") + } + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt index 3d12d9022..b958354d7 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/TroopManagement.kt @@ -13,7 +13,6 @@ import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.buildPacket import kotlinx.io.core.readBytes import kotlinx.io.core.toByteArray -import kotlinx.serialization.toUtf8Bytes import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.data.Packet @@ -410,7 +409,7 @@ internal class TroopManagement { gender = 0, dwuin = member.id, dwFlag = 31, - sName = newName.toUtf8Bytes().encodeToString(charset = CharsetGBK), + sName = newName.toByteArray(CharsetUTF8).encodeToString(CharsetGBK), sPhone = "", sEmail = "", sRemark = "" diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt index fc5ce1a45..6ac6e0b69 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/ImgStore.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.image -import io.ktor.client.HttpClient import kotlinx.io.core.ByteReadPacket import net.mamoe.mirai.data.Packet import net.mamoe.mirai.qqandroid.QQAndroidBot diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt index 668436eaa..b4cf13881 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/image/LongConn.kt @@ -67,7 +67,7 @@ internal class LongConn { } object OffPicDown : OutgoingPacketFactory("LongConn.OffPicDown") { - operator fun invoke(client: QQAndroidClient, req: GetImgUrlReq): OutgoingPacket { + operator fun invoke(client: QQAndroidClient, @Suppress("UNUSED_PARAMETER") req: GetImgUrlReq): OutgoingPacket { return buildOutgoingUniPacket(client) { TODO() } diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt index 7aad3fefa..a711e56df 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/MessageSvc.kt @@ -9,17 +9,19 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.flow.* import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.discardExact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.data.MemberInfo -import net.mamoe.mirai.data.MultiPacket +import net.mamoe.mirai.data.MultiPacketByIterable import net.mamoe.mirai.data.Packet -import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.MemberJoinEvent +import net.mamoe.mirai.getFriendOrNull import net.mamoe.mirai.message.FriendMessage import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.qqandroid.GroupImpl @@ -28,6 +30,8 @@ import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.io.serialization.toByteArray import net.mamoe.mirai.qqandroid.io.serialization.writeProtoBuf +import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendFriend +import net.mamoe.mirai.qqandroid.message.MessageSourceFromSendGroup import net.mamoe.mirai.qqandroid.message.toMessageChain import net.mamoe.mirai.qqandroid.message.toRichTextElems import net.mamoe.mirai.qqandroid.network.QQAndroidClient @@ -71,7 +75,7 @@ internal class MessageSvc { /** * 获取好友消息和消息记录 */ - @UseExperimental(MiraiInternalAPI::class) + @OptIn(MiraiInternalAPI::class) internal object PbGetMsg : OutgoingPacketFactory("MessageSvc.PbGetMsg") { operator fun invoke( client: QQAndroidClient, @@ -94,7 +98,7 @@ internal class MessageSvc { syncFlag = syncFlag, // serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY, syncCookie = client.c2cMessageSync.syncCookie - ?: SyncCookie(time = msgTime + client.timeDifference).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it }, + ?: SyncCookie(time = msgTime).toByteArray(SyncCookie.serializer())//.also { client.c2cMessageSync.syncCookie = it }, // syncFlag = client.c2cMessageSync.syncFlag, //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, //pubaccountCookie = client.c2cMessageSync.pubAccountCookie @@ -102,33 +106,32 @@ internal class MessageSvc { ) } - @UseExperimental(MiraiInternalAPI::class) - internal class GetMsgSuccess(delegate: List) : Response(MsgSvc.SyncFlag.STOP, delegate) - - /** - * 不要直接 expect 这个 class. 它可能 - */ - @MiraiInternalAPI - open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List) : MultiPacket(delegate), - BroadcastControllable { - override val shouldBroadcast: Boolean - get() = syncFlagFromServer == MsgSvc.SyncFlag.STOP - - override fun toString(): String { - return "MessageSvc.PbGetMsg.Response($syncFlagFromServer=$syncFlagFromServer, messages=List(size=${this.size}))" - } + @OptIn(MiraiInternalAPI::class) + open class GetMsgSuccess(delegate: List) : Response(MsgSvc.SyncFlag.STOP, delegate) { + override fun toString(): String = "MessageSvc.PbGetMsg.GetMsgSuccess(messages=))" } - object EmptyResponse : Response(MsgSvc.SyncFlag.STOP, emptyList()) + /** + * 不要直接 expect 这个 class. 它可能还没同步完成 + */ + @MiraiInternalAPI + open class Response(internal val syncFlagFromServer: MsgSvc.SyncFlag, delegate: List) : + MultiPacketByIterable(delegate) { + override fun toString(): String = + "MessageSvc.PbGetMsg.Response($syncFlagFromServer=$syncFlagFromServer, messages=))" + } - @UseExperimental(MiraiInternalAPI::class, MiraiExperimentalAPI::class) + object EmptyResponse : GetMsgSuccess(emptyList()) + + @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class, FlowPreview::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer()) if (resp.result != 0) { - // println("!!! Result=${resp.result} !!!: " + resp.contentToString()) - return GetMsgSuccess(mutableListOf()) + bot.network.logger + .warning("MessageSvc.PushNotify: result != 0, result = ${resp.result}, errorMsg=${resp.errmsg}") + return EmptyResponse } bot.client.c2cMessageSync.syncCookie = resp.syncCookie @@ -139,17 +142,16 @@ internal class MessageSvc { return EmptyResponse } - val messages = resp.uinPairMsgs.asSequence() + val messages = resp.uinPairMsgs.asFlow() .filterNot { it.msg == null } - .flatMap { it.msg!!.asSequence() } - .toList() // so as to inline + .flatMapConcat { it.msg!!.asFlow() } .mapNotNull { msg -> when (msg.msgHead.msgType) { 33 -> { val group = bot.getGroupByUinOrNull(msg.msgHead.fromUin) if (msg.msgHead.authUin == bot.uin) { if (group != null) { - error("group is not null while bot is invited to the group") + return@mapNotNull null } // 新群 @@ -159,12 +161,12 @@ internal class MessageSvc { }.groups.first { it.groupUin == msg.msgHead.fromUin } + @Suppress("DuplicatedCode") val newGroup = GroupImpl( bot = bot, coroutineContext = bot.coroutineContext, id = Group.calculateGroupCodeByGroupUin(msg.msgHead.fromUin), - groupInfo = bot.queryGroupInfo(troopNum.groupCode).apply { - + groupInfo = bot._lowLevelQueryGroupInfo(troopNum.groupCode).apply { this as GroupInfoImpl if (this.delegate.groupName == null) { @@ -181,32 +183,40 @@ internal class MessageSvc { this.delegate.groupCode = troopNum.groupCode }, - members = bot.queryGroupMemberList(troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin) + members = bot._lowLevelQueryGroupMemberList( + troopNum.groupUin, + troopNum.groupCode, + troopNum.dwGroupOwnerUin + ) ) bot.groups.delegate.addLast(newGroup) return@mapNotNull BotJoinGroupEvent(newGroup) } else { - checkNotNull(group) { "group is null while a member is joining to" } + if (group == null) { + return@mapNotNull null + } if (group.members.contains(msg.msgHead.authUin)) { return@mapNotNull null - } else { - return@mapNotNull MemberJoinEvent(group.Member(object : MemberInfo { - override val nameCard: String get() = "" - override val permission: MemberPermission get() = MemberPermission.MEMBER - override val specialTitle: String get() = "" - override val uin: Long get() = msg.msgHead.authUin - override val nick: String get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() } ?: msg.msgHead.fromNick - }).also { group.members.delegate.addLast(it) }) } + return@mapNotNull MemberJoinEvent(group.Member(object : MemberInfo { + override val nameCard: String get() = "" + override val permission: MemberPermission get() = MemberPermission.MEMBER + override val specialTitle: String get() = "" + override val muteTimestamp: Int get() = 0 + override val uin: Long get() = msg.msgHead.authUin + override val nick: String + get() = msg.msgHead.authNick.takeIf { it.isNotEmpty() } + ?: msg.msgHead.fromNick + }).also { group.members.delegate.addLast(it) }) } } 166 -> { + val friend = bot.getFriendOrNull(msg.msgHead.fromUin) ?: return@mapNotNull null return@mapNotNull when { msg.msgHead.fromUin == bot.uin -> null !bot.firstLoginSucceed -> null else -> FriendMessage( - bot, - bot.getFriend(msg.msgHead.fromUin), + friend, msg.toMessageChain() ) } @@ -214,23 +224,27 @@ internal class MessageSvc { else -> return@mapNotNull null } } + + val list: List = messages.toList() if (resp.syncFlag == MsgSvc.SyncFlag.STOP) { - messages.ifEmpty { - return EmptyResponse - } - return GetMsgSuccess(listOf(messages.last())) + return GetMsgSuccess(list) } - return Response(resp.syncFlag, messages) + return Response(resp.syncFlag, list) } override suspend fun QQAndroidBot.handle(packet: Response) { when (packet.syncFlagFromServer) { - MsgSvc.SyncFlag.STOP, - MsgSvc.SyncFlag.START -> return + MsgSvc.SyncFlag.STOP -> return + MsgSvc.SyncFlag.START -> { + network.run { + PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendAndExpect() + } + return + } MsgSvc.SyncFlag.CONTINUE -> { network.run { - PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendWithoutExpect() + PbGetMsg(client, MsgSvc.SyncFlag.CONTINUE, currentTimeSeconds).sendAndExpect() } return } @@ -255,21 +269,44 @@ internal class MessageSvc { override fun toString(): String = "MessageSvc.PbSendMsg.Response.SUCCESS" } + /** + * 121: 被限制? 个别号才不能发 + */ data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() { override fun toString(): String = "MessageSvc.PbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)" } } + inline fun ToFriend( + client: QQAndroidClient, + toUin: Long, + message: MessageChain, + crossinline sourceCallback: (MessageSourceFromSendFriend) -> Unit + ): OutgoingPacket { + val source = MessageSourceFromSendFriend( + messageRandom = Random.nextInt().absoluteValue, + senderId = client.uin, + toUin = toUin, + time = currentTimeSeconds, + groupId = 0, + sequenceId = client.atomicNextMessageSequenceId(), + originalMessage = message + ) + sourceCallback(source) + return ToFriend(client, toUin, message, source) + } + /** * 发送好友消息 */ - fun ToFriend( + @Suppress("FunctionName") + private fun ToFriend( client: QQAndroidClient, toUin: Long, - message: MessageChain + message: MessageChain, + source: MessageSourceFromSendFriend ): OutgoingPacket = buildOutgoingUniPacket(client) { - ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) ///return@buildOutgoingUniPacket @@ -279,31 +316,52 @@ internal class MessageSvc { contentHead = MsgComm.ContentHead(pkgNum = 1), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( - elems = message.toRichTextElems() + elems = message.toRichTextElems(false) ) ), - msgSeq = client.atomicNextMessageSequenceId(), - msgRand = Random.nextInt().absoluteValue, - syncCookie = SyncCookie(time = currentTimeSeconds).toByteArray(SyncCookie.serializer()) + msgSeq = source.sequenceId, + msgRand = source.messageRandom, + syncCookie = SyncCookie(time = source.time).toByteArray(SyncCookie.serializer()) // msgVia = 1 ) ) } + + inline fun ToGroup( + client: QQAndroidClient, + groupCode: Long, + message: MessageChain, + sourceCallback: (MessageSourceFromSendGroup) -> Unit + ): OutgoingPacket { + + val source = MessageSourceFromSendGroup( + messageRandom = Random.nextInt().absoluteValue, + senderId = client.uin, + toUin = Group.calculateGroupUinByGroupCode(groupCode), + time = currentTimeSeconds, + groupId = groupCode, + originalMessage = message//, + // sourceMessage = message + ) + sourceCallback(source) + return ToGroup(client, groupCode, message, source) + } + /** * 发送群消息 */ - fun ToGroup( + @Suppress("FunctionName") + private fun ToGroup( client: QQAndroidClient, groupCode: Long, - message: MessageChain + message: MessageChain, + source: MessageSourceFromSendGroup ): OutgoingPacket = buildOutgoingUniPacket(client) { - ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) // DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString()) - val seq = client.atomicNextMessageSequenceId() ///return@buildOutgoingUniPacket writeProtoBuf( MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( @@ -311,11 +369,11 @@ internal class MessageSvc { contentHead = MsgComm.ContentHead(pkgNum = 1), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( - elems = message.toRichTextElems() + elems = message.toRichTextElems(true) ) ), - msgSeq = seq, - msgRand = Random.nextInt().absoluteValue, + msgSeq = client.atomicNextMessageSequenceId(), + msgRand = source.messageRandom, syncCookie = EMPTY_BYTE_ARRAY, msgVia = 1 ) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt index 87afe2054..97e50fb47 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/chat/receive/OnlinePush.kt @@ -11,18 +11,18 @@ package net.mamoe.mirai.qqandroid.network.protocol.packet.chat.receive -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.readBytes -import kotlinx.io.core.readUInt +import kotlinx.io.core.* import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.data.MultiPacketBySequence import net.mamoe.mirai.data.NoPacket import net.mamoe.mirai.data.Packet -import net.mamoe.mirai.event.broadcast +import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.GroupMessage import net.mamoe.mirai.qqandroid.GroupImpl import net.mamoe.mirai.qqandroid.MemberImpl import net.mamoe.mirai.qqandroid.QQAndroidBot +import net.mamoe.mirai.qqandroid.checkIsInstance import net.mamoe.mirai.qqandroid.io.serialization.decodeUniPacket import net.mamoe.mirai.qqandroid.io.serialization.readProtoBuf import net.mamoe.mirai.qqandroid.message.toMessageChain @@ -31,73 +31,70 @@ import net.mamoe.mirai.qqandroid.network.protocol.data.jce.OnlinePushPack import net.mamoe.mirai.qqandroid.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.qqandroid.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.qqandroid.network.protocol.data.proto.OnlinePushTrans +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.qqandroid.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.debug -import net.mamoe.mirai.utils.io.discardExact import net.mamoe.mirai.utils.io.read import net.mamoe.mirai.utils.io.readString import net.mamoe.mirai.utils.io.toUHexString -internal inline class GroupMessageOrNull(val delegate: GroupMessage?) : Packet { - override fun toString(): String { - return delegate?.toString() ?: "" - } -} - internal class OnlinePush { /** * 接受群消息 */ - internal object PbPushGroupMsg : IncomingPacketFactory("OnlinePush.PbPushGroupMsg") { - @UseExperimental(ExperimentalStdlibApi::class) - override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): GroupMessageOrNull { + internal object PbPushGroupMsg : IncomingPacketFactory("OnlinePush.PbPushGroupMsg") { + internal class SendGroupMessageReceipt( + val messageRandom: Int, + val sequenceId: Int + ) : Packet, Event { + override fun toString(): String { + return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)" + } + } + + @OptIn(ExperimentalStdlibApi::class) + override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 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 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 - if (!bot.firstLoginSucceed) return GroupMessageOrNull(null) + if (!bot.firstLoginSucceed) return null val pbPushMsg = readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()) - val extraInfo: ImMsgBody.ExtraInfo? = pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo + val extraInfo: ImMsgBody.ExtraInfo? = + pbPushMsg.msg.msgBody.richText.elems.firstOrNull { it.extraInfo != null }?.extraInfo if (pbPushMsg.msg.msgHead.fromUin == bot.uin) { - return GroupMessageOrNull(null) + return SendGroupMessageReceipt( + pbPushMsg.msg.msgBody.richText.attr!!.random, + pbPushMsg.msg.msgHead.msgSeq + ) } val group = bot.getGroup(pbPushMsg.msg.msgHead.groupInfo!!.groupCode) // println(pbPushMsg.msg.msgBody.richText.contentToString()) val flags = extraInfo?.flags ?: 0 - return GroupMessageOrNull( - GroupMessage( - bot = bot, - group = group, - senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard, - sender = group[pbPushMsg.msg.msgHead.fromUin], - message = pbPushMsg.msg.toMessageChain(), - permission = when { - flags and 16 != 0 -> MemberPermission.ADMINISTRATOR - flags and 8 != 0 -> MemberPermission.OWNER - flags == 0 -> MemberPermission.MEMBER - else -> { - bot.logger.warning("判断群员权限失败") - MemberPermission.MEMBER - } + return GroupMessage( + senderName = pbPushMsg.msg.msgHead.groupInfo.groupCard, + sender = group[pbPushMsg.msg.msgHead.fromUin], + message = pbPushMsg.msg.toMessageChain(), + permission = when { + flags and 16 != 0 -> MemberPermission.ADMINISTRATOR + flags and 8 != 0 -> MemberPermission.OWNER + flags == 0 -> MemberPermission.MEMBER + else -> { + bot.logger.warning("判断群员权限失败") + MemberPermission.MEMBER } - ) + } ) } - - override suspend fun QQAndroidBot.handle(packet: GroupMessageOrNull, sequenceId: Int): OutgoingPacket? { - packet.delegate?.broadcast() - return null - } - } internal object PbPushTransMsg : IncomingPacketFactory("OnlinePush.PbPushTransMsg", "OnlinePush.RespPush") { - @UseExperimental(MiraiInternalAPI::class) + @OptIn(MiraiInternalAPI::class) @ExperimentalUnsignedTypes override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet { val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer()) @@ -114,28 +111,65 @@ internal class OnlinePush { val group = bot.getGroupByUin(content.fromUin) as GroupImpl if (var5 == 0L && this.remaining == 1L) {//管理员变更 - val newPermission = if (this.readByte().toInt() == 1) MemberPermission.ADMINISTRATOR else MemberPermission.MEMBER + val newPermission = + if (this.readByte() + .toInt() == 1 + ) MemberPermission.ADMINISTRATOR else MemberPermission.MEMBER return if (target == bot.uin) { - BotGroupPermissionChangeEvent(group, group.botPermission.also { group.botPermission = newPermission }, newPermission) + BotGroupPermissionChangeEvent( + group, + group.botPermission.also { group.botPermission = newPermission }, + newPermission + ) } else { val member = group[target] as MemberImpl - MemberPermissionChangeEvent(member, member.permission.also { member.permission = newPermission }, newPermission) + MemberPermissionChangeEvent( + member, + member.permission.also { member.permission = newPermission }, + newPermission + ) } } } 34 -> { - readUInt().toLong() // uin or code ? - if (readByte().toInt() == 1) { - val target = readUInt().toLong() - val groupUin = content.fromUin + /* quit + 27 0B 60 E7 + 01 + 2F 55 7C B8 + 82 + 00 30 42 33 32 46 30 38 33 32 39 32 35 30 31 39 33 45 46 32 45 30 36 35 41 35 41 33 42 37 35 43 41 34 46 37 42 38 42 38 42 44 43 35 35 34 35 44 38 30 + */ + /* kick + 27 0B 60 E7 + 01 + A8 32 51 A1 + 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36 + */ + readUInt().toLong() // group, uin or code ? - bot.getGroupByUin(groupUin).let { group -> - val member = group[target] as MemberImpl - this.discardExact(1) - return MemberLeaveEvent.Kick(member.also { - group.members.delegate.remove(member) - }, group.members[readUInt().toLong()]) + discardExact(1) + val target = readUInt().toLong() + val type = readUByte().toInt() + val operator = readUInt().toLong() + val groupUin = content.fromUin + + when (type) { + 0x82 -> { + bot.getGroupByUin(groupUin).let { group -> + val member = group.getOrNull(target) as? MemberImpl ?: return NoPacket + return MemberLeaveEvent.Quit(member.also { + group.members.delegate.remove(member) + }) + } + } + 0x83 -> { + bot.getGroupByUin(groupUin).let { group -> + val member = group.getOrNull(target) as? MemberImpl ?: return NoPacket + return MemberLeaveEvent.Kick(member.also { + group.members.delegate.remove(member) + }, group.members[operator]) + } } } } @@ -153,135 +187,222 @@ internal class OnlinePush { //0C 01 B1 89 BE 09 5E 3D 72 A6 00 01 73 68 FC 06 00 00 00 3C internal object ReqPush : IncomingPacketFactory("OnlinePush.ReqPush", "OnlinePush.RespPush") { @ExperimentalUnsignedTypes - @UseExperimental(ExperimentalStdlibApi::class) + @OptIn(ExperimentalStdlibApi::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet { val reqPushMsg = decodeUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req") - reqPushMsg.vMsgInfos.forEach { msgInfo: MsgInfo -> - msgInfo.vMsg!!.read { - // TODO: 2020/2/13 可能会同时收到多个事件. 使用 map 而不要直接 return - when { - msgInfo.shMsgType.toInt() == 732 -> { - val group = bot.getGroup(this.readUInt().toLong()) - group as GroupImpl + val packets: Sequence = + reqPushMsg.vMsgInfos.asSequence().flatMap { msgInfo: MsgInfo -> + msgInfo.vMsg!!.read { + when (msgInfo.shMsgType.toInt()) { + 732 -> { + val group = bot.getGroup(this.readUInt().toLong()) + GroupImpl.checkIsInstance(group is GroupImpl) - when (val internalType = this.readShort().toInt()) { - 3073 -> { // mute - val operatorUin = this.readUInt().toLong() - if (operatorUin == bot.uin) { - return NoPacket - } - val operator = group[operatorUin] - this.readUInt().toLong() // time - this.discardExact(2) - val target = this.readUInt().toLong() - val time = this.readInt() - - return if (target == 0L) { - if (time == 0) { - GroupMuteAllEvent( - origin = group.isMuteAll.also { group._muteAll = false }, - new = false, - operator = operator, - group = group - ) - } else { - GroupMuteAllEvent( - origin = group.isMuteAll.also { group._muteAll = true }, - new = true, - operator = operator, - group = group - ) + when (val internalType = this.readByte().toInt().also { this.discardExact(1) }) { + 0x0c -> { // mute + val operatorUin = this.readUInt().toLong() + if (operatorUin == bot.uin) { + return@flatMap sequenceOf() } - } else { - return if (target == bot.uin) { + val operator = group.getOrNull(operatorUin) ?: return@flatMap sequenceOf() + this.readUInt().toLong() // time + this.discardExact(2) + val target = this.readUInt().toLong() + val time = this.readInt() + + if (target == 0L) { if (time == 0) { - BotUnmuteEvent(operator) - } else - BotMuteEvent(durationSeconds = time, operator = operator) - } else { - val member = group[target] - if (time == 0) { - MemberUnmuteEvent(operator = operator, member = member) + return@flatMap sequenceOf( + GroupMuteAllEvent( + origin = group.isMuteAll.also { group._muteAll = false }, + new = false, + operator = operator, + group = group + ) + ) } else { - MemberMuteEvent(operator = operator, member = member, durationSeconds = time) + return@flatMap sequenceOf( + GroupMuteAllEvent( + origin = group.isMuteAll.also { group._muteAll = true }, + new = true, + operator = operator, + group = group + ) + ) } - } - } - } - 3585 -> { // 匿名 - val operator = group[this.readUInt().toLong()] - val switch = this.readInt() == 0 - return GroupAllowAnonymousChatEvent( - origin = group.isAnonymousChatEnabled.also { group._anonymousChat = switch }, - new = switch, - operator = operator, - group = group - ) - } - 4096 -> { - val dataBytes = this.readBytes(26) - val message = this.readString(this.readByte().toInt()) - // println(dataBytes.toUHexString()) + } else { + if (target == bot.uin) { + if (group._botMuteTimestamp == time) { + return@flatMap sequenceOf() + } + if (time == 0) { + group._botMuteTimestamp = 0 + return@flatMap sequenceOf(BotUnmuteEvent(operator)) + } else { + group._botMuteTimestamp = time + return@flatMap sequenceOf( + BotMuteEvent( + durationSeconds = time, + operator = operator + ) + ) + } + } else { + val member = group.getOrNull(target) ?: return@flatMap sequenceOf() + member as MemberImpl + if (member._muteTimestamp == time) { + return@flatMap sequenceOf() + } - if (dataBytes[0].toInt() != 59) { - return GroupNameChangeEvent( - origin = group.name.also { group._name = message }, - new = message, - group = group, - isByBot = false - ) - } else { - //println(message + ":" + dataBytes.toUHexString()) - when (message) { - "管理员已关闭群聊坦白说" -> { - return GroupAllowConfessTalkEvent( - origin = group.isConfessTalkEnabled.also { group._confessTalk = false }, - new = false, - group = group, - isByBot = false - ) - } - "管理员已开启群聊坦白说" -> { - return GroupAllowConfessTalkEvent( - origin = group.isConfessTalkEnabled.also { group._confessTalk = true }, - new = true, - group = group, - isByBot = false - ) - } - else -> { - bot.network.logger.debug { "Unknown server messages $message" } - return NoPacket + if (time == 0) { + member._muteTimestamp = 0 + return@flatMap sequenceOf( + MemberUnmuteEvent( + member, + operator + ) + ) + } else { + member._muteTimestamp = time + return@flatMap sequenceOf( + MemberMuteEvent( + member, + time, + operator + ) + ) + } } } } - } - // 4352 -> { - // println(msgInfo.contentToString()) - // println(msgInfo.vMsg.toUHexString()) - // } - else -> { - bot.network.logger.debug { "unknown group internal type $internalType , data: " + this.readBytes().toUHexString() + " " } + 0x0e -> { + // 匿名 + val operator = + group.getOrNull(this.readUInt().toLong()) ?: return@flatMap sequenceOf() + val switch = this.readInt() == 0 + return@flatMap sequenceOf( + GroupAllowAnonymousChatEvent( + origin = group.isAnonymousChatEnabled.also { + group._anonymousChat = switch + }, + new = switch, + operator = operator, + group = group + ) + ) + } + 0x10 -> { + val dataBytes = this.readBytes(26) + val message = this.readString(this.readByte().toInt()) + // println(dataBytes.toUHexString()) + + if (dataBytes[0].toInt() != 59) { + return@flatMap sequenceOf( + GroupNameChangeEvent( + origin = group.name.also { group._name = message }, + new = message, + group = group, + isByBot = false + ) + ) + } else { + //println(message + ":" + dataBytes.toUHexString()) + when (message) { + "管理员已关闭群聊坦白说" -> { + return@flatMap sequenceOf( + GroupAllowConfessTalkEvent( + origin = group.isConfessTalkEnabled.also { + group._confessTalk = false + }, + new = false, + group = group, + isByBot = false + ) + ) + } + "管理员已开启群聊坦白说" -> { + return@flatMap sequenceOf( + GroupAllowConfessTalkEvent( + origin = group.isConfessTalkEnabled.also { + group._confessTalk = true + }, + new = true, + group = group, + isByBot = false + ) + ) + } + else -> { + bot.network.logger.debug { "Unknown server messages $message" } + return@flatMap sequenceOf() + } + } + } + } + 0x11 -> { + discardExact(1) + val proto = readProtoBuf(TroopTips0x857.NotifyMsgBody.serializer()) + proto.optMsgRecall?.let { recallReminder -> + + val memebr = + group.getOrNull(recallReminder.uin) ?: return@flatMap sequenceOf() + return@flatMap recallReminder.recalledMsgList.asSequence() + .mapNotNull { meta -> + if (meta.authorUin == bot.uin) { + null + } else MessageRecallEvent.GroupRecall( + bot, + meta.authorUin, + meta.seq.toLong().shl(32) or + meta.msgRandom.toLong().and(0xffffffff), + meta.time, + memebr, + group + ) + } + } + + return@flatMap sequenceOf() + } + // 4352 -> { + // println(msgInfo.contentToString()) + // println(msgInfo.vMsg.toUHexString()) + // } + else -> { + bot.network.logger.debug { + "unknown group internal type $internalType , data: " + this.readBytes() + .toUHexString() + " " + } + return@flatMap sequenceOf() + } } } - } - msgInfo.shMsgType.toInt() == 528 -> { - bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" } - // val content = msgInfo.vMsg.loadAs(OnlinePushPack.MsgType0x210.serializer()) - // println(content.contentToString()) - } - else -> { - bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" } + + /* 528 -> { + val notifyMsgBody = msgInfo.vMsg.loadAs(Oidb0x858.NotifyMsgBody.serializer()) + notifyMsgBody.optMsgRecallReminder?.let { messageRecallReminder -> + return@flatMap messageRecallReminder.recalledMsgList.asSequence().map { + MessageRecallEvent.FriendRecall( + bot, + it.seq.toLong().shl(32) or it.msgRandom.toLong().and(0xffffffff), + it.time, + messageRecallReminder.uin + ) + } + } + return@flatMap sequenceOf() + }*/ + else -> { + bot.network.logger.debug { "unknown shtype ${msgInfo.shMsgType.toInt()}" } + return@flatMap sequenceOf() + } } } } - } - - return NoPacket + return MultiPacketBySequence(packets) } - override suspend fun QQAndroidBot.handle(packet: Packet, sequenceId: Int): OutgoingPacket? { return buildResponseUniPacket(client, sequenceId = sequenceId) { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendList.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendList.kt index f2ab4eef9..411e2f7b5 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendList.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/list/FriendList.kt @@ -123,7 +123,8 @@ internal class FriendList { } } - internal object GetFriendGroupList : OutgoingPacketFactory("friendlist.getFriendGroupList") { + internal object GetFriendGroupList : + OutgoingPacketFactory("friendlist.getFriendGroupList") { class Response( val totalFriendCount: Short, diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt index c513076b4..d4f65ae25 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/StatSvc.kt @@ -16,16 +16,17 @@ import net.mamoe.mirai.qqandroid.io.serialization.* import net.mamoe.mirai.qqandroid.network.QQAndroidClient import net.mamoe.mirai.qqandroid.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.qqandroid.network.protocol.data.jce.SvcReqRegister +import net.mamoe.mirai.qqandroid.network.protocol.data.proto.Oidb0x769 import net.mamoe.mirai.qqandroid.network.protocol.data.proto.StatSvcGetOnline import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.qqandroid.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.qqandroid.network.protocol.packet.buildLoginOutgoingPacket -import net.mamoe.mirai.qqandroid.network.protocol.packet.oidb.oidb0x769.Oidb0x769 import net.mamoe.mirai.qqandroid.network.protocol.packet.writeSsoPacket import net.mamoe.mirai.qqandroid.utils.NetworkType +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiPlatformUtils import net.mamoe.mirai.utils.io.encodeToString import net.mamoe.mirai.utils.io.toReadPacket -import net.mamoe.mirai.utils.localIpAddress @Suppress("EnumEntryName") internal enum class RegPushReason { @@ -89,6 +90,7 @@ internal class StatSvc { private const val subAppId = 537062845L + @OptIn(MiraiInternalAPI::class) operator fun invoke( client: QQAndroidClient, regPushReason: RegPushReason = RegPushReason.appRegister @@ -138,7 +140,7 @@ internal class StatSvc { strOSVer = client.device.version.release.encodeToString(), uOldSSOIp = 0, - uNewSSOIp = localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String -> + uNewSSOIp = MiraiPlatformUtils.localIpAddress().split(".").foldIndexed(0L) { index: Int, acc: Long, s: String -> acc or ((s.toLong() shl (index * 16))) }, strVendorName = "MIUI", diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt index 6c7c5b26b..5b7365394 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/login/WtLogin.kt @@ -20,21 +20,16 @@ import net.mamoe.mirai.qqandroid.network.protocol.packet.* import net.mamoe.mirai.qqandroid.utils.GuidSource import net.mamoe.mirai.qqandroid.utils.MacOrAndroidIdChangeFlag import net.mamoe.mirai.qqandroid.utils.guidFlag -import net.mamoe.mirai.utils.MiraiDebugAPI -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.cryptor.contentToString -import net.mamoe.mirai.utils.cryptor.decryptBy -import net.mamoe.mirai.utils.currentTimeSeconds +import net.mamoe.mirai.utils.* +import net.mamoe.mirai.utils.cryptor.TEA import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.io.discardExact -import net.mamoe.mirai.utils.md5 internal class WtLogin { /** * OicqRequest */ @Suppress("FunctionName") - @UseExperimental(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) + @OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) internal object Login : OutgoingPacketFactory("wtlogin.login") { private const val subAppId = 537062845L @@ -47,7 +42,7 @@ internal class WtLogin { ticket: String ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { - writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { + writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(2) // subCommand writeShort(4) // count of TLVs t193(ticket) @@ -64,7 +59,7 @@ internal class WtLogin { captchaAnswer: String ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { - writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { + writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(2) // subCommand writeShort(4) // count of TLVs t2(captchaAnswer, captchaSign, 0) @@ -83,13 +78,13 @@ internal class WtLogin { t402: ByteArray ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { - writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { + writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(20) // subCommand writeShort(4) // count of TLVs, probably ignored by server? t8(2052) t104(client.t104) t116(150470524, 66560) - t401(md5(client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402)) + t401(MiraiPlatformUtils.md5(client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402)) } } } @@ -102,8 +97,14 @@ internal class WtLogin { operator fun invoke( client: QQAndroidClient ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> - writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId, unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00") { - writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { + writeSsoPacket( + client, + subAppId, + commandName, + sequenceId = sequenceId, + unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00" + ) { + writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(8) // subCommand writeShort(6) // count of TLVs, probably ignored by server?TODO t8(2052) @@ -126,12 +127,12 @@ internal class WtLogin { private const val appId = 16L private const val subAppId = 537062845L - @UseExperimental(MiraiInternalAPI::class) + @OptIn(MiraiInternalAPI::class, MiraiExperimentalAPI::class) operator fun invoke( client: QQAndroidClient ): OutgoingPacket = buildLoginOutgoingPacket(client, bodyType = 2) { sequenceId -> writeSsoPacket(client, subAppId, commandName, sequenceId = sequenceId) { - writeOicqRequestPacket(client, EncryptMethodECDH7(client.ecdh), 0x0810) { + writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeShort(9) // subCommand writeShort(17) // count of TLVs, probably ignored by server? //writeShort(LoginType.PASSWORD.value.toShort()) @@ -287,7 +288,7 @@ internal class WtLogin { } class Picture( - val data: IoBuffer, + val data: ByteArray, val sign: ByteArray ) : Captcha() { override fun toString(): String = "LoginPacketResponse.Captcha.Picture" @@ -308,7 +309,7 @@ internal class WtLogin { } @InternalAPI - @UseExperimental(MiraiDebugAPI::class) + @OptIn(MiraiDebugAPI::class) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse { discardExact(2) // subCommand @@ -317,15 +318,18 @@ internal class WtLogin { // println("type=$type") discardExact(2) - val tlvMap: TlvMap = this.readTLVMap() + val tlvMap: TlvMap = this._readTLVMap() // tlvMap.printTLVMap() return when (type.toInt()) { 0 -> onLoginSuccess(tlvMap, bot) - 1, 15 -> onErrorMessage(tlvMap) 2 -> onSolveLoginCaptcha(tlvMap, bot) 160 /*-96*/ -> onUnsafeDeviceLogin(tlvMap) 204 /*-52*/ -> onSMSVerifyNeeded(tlvMap, bot) - else -> tlvMap[0x149]?.let { analysisTlv149(it) } ?: error("unknown login result type: $type") + // 1, 15 -> onErrorMessage(tlvMap) ?: error("Cannot find error message") + else -> { + onErrorMessage(tlvMap) + ?: error("Cannot find error message, unknown login result type: $type, TLVMap = ${tlvMap._miraiContentToString()}") + } } } @@ -343,21 +347,28 @@ internal class WtLogin { return LoginPacketResponse.UnsafeLogin(tlvMap.getOrFail(0x204).toReadPacket().readBytes().encodeToString()) } - private fun onErrorMessage(tlvMap: TlvMap): LoginPacketResponse.Error { - return tlvMap[0x146]?.toReadPacket()?.run { - readShort() // ver - readShort() // code + private fun onErrorMessage(tlvMap: TlvMap): LoginPacketResponse.Error? { + return tlvMap[0x149]?.read { + discardExact(2) //type + val title: String = readUShortLVString() + val content: String = readUShortLVString() + val otherInfo: String = readUShortLVString() + + LoginPacketResponse.Error(title, content, otherInfo) + } ?: tlvMap[0x146]?.read { + discardExact(2) // ver + discardExact(2) // code val title = readUShortLVString() val message = readUShortLVString() val errorInfo = readUShortLVString() LoginPacketResponse.Error(title, message, errorInfo) - } ?: error("Cannot find error message") + } } @InternalAPI - @UseExperimental(MiraiDebugAPI::class) + @OptIn(MiraiDebugAPI::class) private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha { /* java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION: @@ -383,20 +394,17 @@ internal class WtLogin { imageData.discardExact(2)//image Length val sign = imageData.readBytes(signInfoLength.toInt()) - - val buffer = IoBuffer.Pool.borrow() - imageData.readAvailable(buffer) return LoginPacketResponse.Captcha.Picture( - data = buffer, + data = imageData.readBytes(), sign = sign ) // } else error("UNKNOWN CAPTCHA QUESTION: ${question.toUHexString()}, tlvMap=" + tlvMap.contentToString()) } - error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap.contentToString()) + error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap._miraiContentToString()) } - @UseExperimental(MiraiDebugAPI::class) + @OptIn(MiraiDebugAPI::class) private fun onLoginSuccess(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success { val client = bot.client //println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() }) @@ -405,17 +413,17 @@ internal class WtLogin { // tlvMap[0x305]?.let { println("TLV 0x305=${it.toUHexString()}") } tlvMap[0x161]?.let { client.analysisTlv161(it) } tlvMap[0x119]?.let { t119Data -> - t119Data.decryptBy(client.tgtgtKey).read { + TEA.decrypt(t119Data, client.tgtgtKey).read { discardExact(2) // always discarded. 00 1C // 00 1C // 01 08 00 10 A1 73 76 98 64 E0 38 C6 C8 18 73 FA D3 85 DA D6 01 6A 00 30 1D 99 4A 28 7E B3 B8 AC 74 B9 C4 BB 6D BB 41 72 F7 5C 9F 0F 79 8A 82 4F 1F 69 34 6D 10 D6 BB E8 A3 4A 2B 5D F1 C7 05 3C F8 72 EF CF 67 E4 3C 94 01 06 00 78 B4 ED 9F 44 ED 10 18 A8 85 0A 8A 85 79 45 47 7F 25 AA EE 2C 53 83 80 0A B3 B0 47 3E 95 51 A4 AE 3E CA A0 1D B4 91 F7 BB 2E 94 76 A8 C8 97 02 C4 5B 15 02 B7 03 9A FC C2 58 6D 17 92 46 AE EB 2F 6F 65 B8 69 6C D6 9D AC 18 6F 07 53 AC FE FA BC BD CE 57 13 10 2D 5A C6 50 AA C2 AE 18 D4 FD CD F2 E0 D1 25 29 56 21 35 8F 01 9D D6 69 44 8F 06 D0 23 26 D3 0E E6 E6 B7 01 0C 00 10 73 32 61 4E 2C 72 35 58 68 28 47 3E 2B 6E 52 62 01 0A 00 48 A4 DA 48 FB B4 8D DA 7B 86 D7 A7 FE 01 1B 70 6F 54 F8 55 38 B0 AD 1B 0C 0B B9 F6 94 24 F8 9E 30 32 22 99 0C 22 CD 44 B8 B0 8A A8 65 E1 B8 F0 49 EF E1 23 D7 0D A3 F1 BB 52 B7 4B AF BD 50 EA BF 15 02 78 2B 8B 10 FB 15 01 0D 00 10 29 75 38 72 21 5D 3F 24 37 46 67 79 2B 65 6D 34 01 14 00 60 00 01 5E 19 65 8C 00 58 93 DD 4D 2C 2D 01 44 99 62 B8 7A EF 04 C5 71 0B F1 BE 4C F4 21 F2 97 B0 14 67 0E 14 9F D8 A2 0B 93 40 90 80 F3 59 7A 69 45 D7 D4 53 4C 08 3A 56 1D C9 95 36 2C 7C 5E EE 36 47 5F AE 26 72 76 FD FD 69 E6 0C 2D 3A E8 CF D4 8D 76 C9 17 C3 E3 CD 21 AB 04 6B 70 C5 EC EC 01 0E 00 10 56 48 3E 29 3A 5A 21 74 55 6A 2C 72 58 73 79 71 01 03 00 30 9B A6 5D 85 5C 40 7C 28 E7 05 A9 25 CA F5 FC C0 51 40 85 F3 2F D2 37 F9 09 A6 E6 56 7F 7A 2E 7D 9F B9 1C 00 65 55 D2 A9 60 03 77 AB 6A F5 3F CE 01 33 00 30 F4 3A A7 08 E2 04 FA C8 9D 54 49 DE 63 EA F0 A5 1C C4 03 57 51 B6 AE 0B 55 41 F8 AB 22 F1 DC A3 B0 73 08 55 14 02 BF FF 55 87 42 4C 23 70 91 6A 01 34 00 10 61 C7 02 3F 1D BE A6 27 2F 24 D4 92 95 68 71 EF 05 28 00 1A 7B 22 51 49 4D 5F 69 6E 76 69 74 61 74 69 6F 6E 5F 62 69 74 22 3A 22 31 22 7D 03 22 00 10 CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40 01 1D 00 76 5F 5E 10 E2 34 36 79 27 23 53 4D 65 6B 6A 33 6D 7D 4E 3C 5F 00 60 00 01 5E 19 65 8C 00 58 67 00 9C 02 E4 BC DB A3 93 98 A1 ED 4C 91 08 6F 0C 06 E0 12 6A DC 14 5B 4D 20 7C 82 83 AE 94 53 A2 4A A0 35 FF 59 9D F3 EF 82 42 61 67 2A 31 E7 87 7E 74 E7 A3 E7 5C A8 3C 87 CF 40 6A 9F E5 F7 20 4E 56 C6 4F 1C 98 3A 8B A9 4F 1D 10 35 C2 3B A1 08 7A 89 0B 25 0C 63 01 1F 00 0A 00 01 51 80 00 00 03 84 00 00 01 38 00 0E 00 00 00 01 01 0A 00 27 8D 00 00 00 00 00 01 1A 00 13 02 5B 06 01 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 05 22 00 14 00 00 00 00 76 E4 B8 DD AB 53 02 9F 5E 19 65 8C 20 02 ED BD 05 37 00 17 01 01 00 00 00 00 76 E4 B8 DD 04 AB 53 02 9F 5E 19 65 8C 20 02 ED BD 01 20 00 0A 4D 39 50 57 50 6E 4C 31 65 4F 01 6D 00 2C 31 7A 50 7A 63 72 70 4D 30 43 6E 31 37 4C 32 32 6E 77 2D 36 7A 4E 71 48 48 59 41 35 48 71 77 41 37 6D 76 4F 63 2D 4A 56 77 47 51 5F 05 12 03 5D 00 0E 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 00 2C 6E 4A 72 55 55 74 63 2A 34 7A 32 76 31 66 6A 75 77 6F 6A 65 73 72 76 4F 68 70 66 45 76 4A 75 55 4B 6D 34 43 2D 76 74 38 4D 77 38 5F 00 00 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 00 2C 78 59 35 65 62 4D 74 48 44 6D 30 53 6F 68 56 71 68 33 43 79 79 34 6F 63 65 4A 46 6A 51 58 65 68 30 44 61 75 55 30 6C 78 65 52 6B 5F 00 00 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 00 2C 64 6A 62 79 47 57 45 4F 34 58 34 6A 36 4A 73 48 45 65 6B 73 69 74 72 78 79 62 57 69 77 49 68 46 45 70 72 4A 59 4F 2D 6B 36 47 6F 5F 00 00 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 00 2C 64 4C 31 41 79 32 41 31 74 33 58 36 58 58 2A 74 33 64 4E 70 2A 31 61 2D 50 7A 65 57 67 48 70 2D 65 47 78 6B 59 74 71 62 69 6C 55 5F 00 00 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 2C 75 6A 55 5A 4F 6A 4F 48 52 61 75 6B 32 55 50 38 77 33 34 68 36 69 46 38 2A 77 4E 50 35 2D 66 54 75 37 67 39 56 67 44 57 2A 6B 6F 5F 00 00 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 00 2C 37 47 31 44 6F 54 2D 4D 57 50 63 2D 62 43 46 68 63 62 32 56 38 6E 77 4A 75 41 51 63 54 39 77 45 49 62 57 43 4A 4B 44 4D 6C 6D 34 5F 00 00 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 2C 7A 73 70 5A 56 43 59 45 7A 35 2A 4F 6B 4E 68 6E 74 79 61 69 6E 6F 68 4D 32 6B 41 6C 2A 74 31 63 7A 48 57 77 30 41 6A 4B 50 4B 6B 5F 00 00 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 00 2C 32 6F 2D 51 53 36 65 43 70 37 6A 43 4E 34 6A 74 6E 47 4F 4B 33 67 73 32 63 4A 6F 56 71 58 65 44 48 61 55 39 65 34 2D 32 34 64 30 5F 00 00 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 00 2C 63 54 4D 79 64 51 43 35 50 74 43 45 51 72 6F 33 53 54 41 66 7A 56 2D 44 76 46 56 35 58 6D 56 6B 49 31 68 4C 55 48 4E 65 76 56 38 5F 00 00 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 00 2C 6F 73 72 54 36 32 69 37 66 76 6D 49 50 64 6F 58 4B 48 74 38 58 52 59 56 77 72 7A 6E 69 31 58 7A 57 4C 77 2A 71 36 33 44 74 73 6F 5F 00 00 00 09 74 69 2E 71 71 2E 63 6F 6D 00 2C 41 61 77 4D 78 4D 32 79 58 51 47 75 72 75 55 6C 66 53 58 79 5A 57 48 53 78 52 57 58 50 74 6B 6B 4F 78 6F 66 4A 59 47 6C 71 68 34 5F 00 00 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 00 2C 67 72 57 68 58 77 34 4C 6E 4B 49 4F 67 63 78 45 71 70 33 61 45 67 37 38 46 7A 77 4E 6D 4B 48 56 6E 6F 50 4C 4F 32 6D 57 6D 6E 38 5F 00 00 00 09 71 7A 6F 6E 65 2E 63 6F 6D 00 2C 72 61 47 79 51 35 54 72 4D 55 7A 6E 74 31 4E 52 44 2D 50 72 74 72 41 55 43 35 6A 61 2D 49 47 2D 73 77 4C 6D 49 51 51 41 44 4C 41 5F 00 00 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 00 2C 39 73 2D 4F 51 30 67 76 39 42 6A 37 58 71 52 49 4E 30 35 46 32 64 4D 47 67 47 43 58 57 4A 62 68 63 30 38 63 7A 4B 52 76 6B 78 6B 5F 00 00 03 05 00 10 77 75 6E 54 5F 7E 66 7A 72 40 3C 6E 35 50 53 46 01 43 00 40 3A AE 30 87 81 3D EE BA 31 9C EA 9D 0D D4 73 B1 81 12 E0 94 71 73 7A B0 47 3D 09 47 E5 1B E1 E2 06 1A CB A4 E3 71 9E A6 EA 2A 73 5C C8 D3 B1 2A B1 C7 DA 04 A6 6D 12 26 DF 6B 8B EC C7 12 F8 E1 01 18 00 05 00 00 00 01 00 01 63 00 10 67 6B 60 23 24 6A 55 39 4E 58 24 5E 39 2B 7A 69 01 38 00 5E 00 00 00 09 01 06 00 27 8D 00 00 00 00 00 01 0A 00 24 EA 00 00 00 00 00 01 1C 00 1A 5E 00 00 00 00 00 01 02 00 01 51 80 00 00 00 00 01 03 00 00 1C 20 00 00 00 00 01 20 00 01 51 80 00 00 00 00 01 36 00 1B AF 80 00 00 00 00 01 43 00 1B AF 80 00 00 00 00 01 64 00 1B AF 80 00 00 00 00 01 30 00 0E 00 00 5E 19 65 8C 9F 02 53 AB 00 00 00 00 - val tlvMap119 = this.readTLVMap() + val tlvMap119 = this._readTLVMap() // ??? tlvMap119[0x1c]?.read { val bytes = readBytes() - DebugLogger.warning(bytes.toUHexString()) - DebugLogger.warning(bytes.encodeToString()) + bot.network.logger.debug("onLoginSuccess, tlvMap119[0x1c]: " + bytes.toUHexString()) + bot.network.logger.debug("onLoginSuccess, tlvMap119[0x1c]: " + bytes.encodeToString()) } tlvMap119[0x130]?.let { client.analysisTlv130(it) } @@ -490,7 +498,7 @@ internal class WtLogin { face = readUShort().toInt() age = readUByte().toInt() gender = readUByte().toInt() - nick = readUByteLVString() + nick = readString(readByte().toInt() and 0xff) } } @@ -560,7 +568,8 @@ internal class WtLogin { imgUrl = client.reserveUinInfo?.imgUrl ?: byteArrayOf(), mainDisplayName = tlvMap119[0x118] ?: error("Cannot find tlv 0x118") ), - appPri = tlvMap119[0x11f]?.let { it.read { discardExact(4); readUInt().toLong() } } ?: 4294967295L, + appPri = tlvMap119[0x11f]?.let { it.read { discardExact(4); readUInt().toLong() } } + ?: 4294967295L, a2ExpiryTime = expireTime, loginBitmap = 0, // from asyncContext._login_bitmap tgt = tlvMap119.getOrEmpty(0x10a), @@ -605,7 +614,7 @@ internal class WtLogin { } private inline fun analysisTlv0x531(t531: ByteArray, handler: (a1: ByteArray, noPicSig: ByteArray) -> Unit) { - val map = t531.toReadPacket().readTLVMap() + val map = t531.toReadPacket()._readTLVMap() val t106 = map[0x106] val t16a = map[0x16a] @@ -621,7 +630,7 @@ internal class WtLogin { * @throws error */ private fun QQAndroidClient.parseWFastLoginInfoDataOutA1(t169: ByteArray): ByteReadPacket { - val map = t169.toReadPacket().readTLVMap() + val map = t169.toReadPacket()._readTLVMap() val t106 = map[0x106] val t10c = map[0x10c] @@ -656,7 +665,7 @@ internal class WtLogin { //discardExact(2) loginExtraData = LoginExtraData( // args are to correct order uin = readUInt().toLong(), - ip = readUByteLVByteArray(), + ip = readBytes(readByte().toInt() and 0xff), time = readInt(), // correct version = readInt() ) @@ -699,29 +708,13 @@ internal class WtLogin { } private fun QQAndroidClient.analysisTlv161(t161: ByteArray) { - val tlv = t161.toReadPacket().apply { discardExact(2) }.readTLVMap() + val tlv = t161.toReadPacket().apply { discardExact(2) }._readTLVMap() tlv[0x173]?.let { analysisTlv173(it) } tlv[0x17f]?.let { analysisTlv17f(it) } tlv[0x172]?.let { rollbackSig = it } } - /** - * 错误消息 - */ - private fun analysisTlv149(t149: ByteArray): LoginPacketResponse.Error { - - return t149.read { - discardExact(2) //type - val title: String = readUShortLVString() - val content: String = readUShortLVString() - val otherInfo: String = readUShortLVString() - - // do not write class into read{} block. CompilationException!! - LoginPacketResponse.Error(title = title, message = content, errorInfo = otherInfo) // nice toString - } - } - /** * server host */ @@ -750,4 +743,6 @@ internal class WtLogin { } } } -} \ No newline at end of file +} + +private fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray()) \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/command/GetPublicAccountDetailInfo.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/command/GetPublicAccountDetailInfo.kt deleted file mode 100644 index deb421ac8..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/command/GetPublicAccountDetailInfo.kt +++ /dev/null @@ -1,28 +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.protocol.packet.oidb.command - -import kotlinx.serialization.Serializable -import net.mamoe.mirai.qqandroid.network.protocol.packet.MessageMicro - -/** - * oidb_cmd0xcf8$GetPublicAccountDetailInfoRequest - */ -@Serializable -class GetPublicAccountDetailInfoRequest( - val seqno: Int, // uint - val uinLong: Long, - val version: Int, // uint - val versionInfo: String -) : MessageMicro - -class GetPublicAccountDetailInfoResponse( - -) : MessageMicro \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/sso/OidbSsoPackage.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/sso/OidbSsoPackage.kt deleted file mode 100644 index 8cec0b26e..000000000 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/network/protocol/packet/oidb/sso/OidbSsoPackage.kt +++ /dev/null @@ -1,31 +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.protocol.packet.oidb.sso - -import kotlinx.serialization.SerialId -import kotlinx.serialization.Serializable -import net.mamoe.mirai.qqandroid.network.protocol.packet.MessageMicro - -/** - * oidb_sso$OIDBSSOPkg - */ -@Serializable -class OidbSsoPackage( - @SerialId(1) val command: Int, // uint - @SerialId(2) val serviceType: Int, // uint - @SerialId(3) val result: Int, // uint - @SerialId(4) val bodyBuffer: ByteArray, - @SerialId(5) val errorMessage: String, - @SerialId(6) val clientVersion: String -) : MessageMicro - - - - diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/Guid.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/GuidSource.kt similarity index 95% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/Guid.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/GuidSource.kt index ee2085cec..f2405cde6 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/Guid.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/GuidSource.kt @@ -7,9 +7,13 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmName("Utils") +@file:JvmMultifileClass + package net.mamoe.mirai.qqandroid.utils -import net.mamoe.mirai.utils.md5 +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic /** diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/Flags.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/flags.kt similarity index 88% rename from mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/Flags.kt rename to mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/flags.kt index a79cba42a..141d44bea 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/Flags.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/flags.kt @@ -7,8 +7,14 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmName("Utils") +@file:JvmMultifileClass + package net.mamoe.mirai.qqandroid.utils +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + inline class MacOrAndroidIdChangeFlag(val value: Long = 0) { fun macChanged(): MacOrAndroidIdChangeFlag = MacOrAndroidIdChangeFlag(this.value or 0x1) diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/inline.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/inline.kt index 26d1d44a0..3b41ac790 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/inline.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/inline.kt @@ -7,16 +7,21 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmName("Utils") +@file:JvmMultifileClass + package net.mamoe.mirai.qqandroid.utils import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName /** * Inline the block */ -@UseExperimental(ExperimentalContracts::class) +@OptIn(ExperimentalContracts::class) @PublishedApi internal inline fun inline(block: () -> R): R { contract { diff --git a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/type.kt b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/type.kt index b295ccfa7..08dfe3c07 100644 --- a/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/type.kt +++ b/mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid/utils/type.kt @@ -7,10 +7,16 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmName("Utils") +@file:JvmMultifileClass + package net.mamoe.mirai.qqandroid.utils +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName -fun Int.toIpV4AddressString(): String { + +internal fun Int.toIpV4AddressString(): String { @Suppress("NAME_SHADOWING") var var0 = this.toLong() and 0xFFFFFFFF return buildString { diff --git a/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt new file mode 100644 index 000000000..36d421604 --- /dev/null +++ b/mirai-core-qqandroid/src/commonTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceInputTest.kt @@ -0,0 +1,471 @@ +@file:Suppress("unused", "DEPRECATION_ERROR") + +package net.mamoe.mirai.qqandroid.io.serialization + +import kotlinx.io.core.buildPacket +import kotlinx.io.core.toByteArray +import kotlinx.io.core.writeFully +import kotlinx.serialization.MissingFieldException +import kotlinx.serialization.Serializable +import net.mamoe.mirai.qqandroid.io.serialization.jce.Jce +import net.mamoe.mirai.qqandroid.io.serialization.jce.JceId +import net.mamoe.mirai.qqandroid.io.serialization.jce.JceInput +import net.mamoe.mirai.qqandroid.io.serialization.jce.writeJceHead +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +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 + +/* + * 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 + */ +@Suppress("INVISIBLE_MEMBER") // bug +internal class JceInputTest { + + @Test + fun testFuckingComprehensiveStruct() { + @Serializable + data class TestSerializableClassC( + @JceId(5) val value3: Int = 123123 + ) + + @Serializable + data class TestSerializableClassB( + @JceId(0) val value: Int, + @JceId(123) val nested2: TestSerializableClassC, + @JceId(5) val value5: Int + ) + + @Serializable + data class TestSerializableClassA( + @JceId(0) val map: Map + ) + + + val input = buildPacket { + writeJceHead(MAP, 0) // TestSerializableClassB + writeJceHead(BYTE, 0) + writeByte(1) + + writeJceHead(STRUCT_BEGIN, 0); + { + writeJceHead(INT, 0) + writeInt(123) + + writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC + { + writeJceHead(INT, 5) + writeInt(123123) + }() + writeJceHead(STRUCT_END, 0) + + writeJceHead(INT, 5) + writeInt(9) + }() + writeJceHead(STRUCT_END, 0) + + writeJceHead(STRUCT_BEGIN, 1); + { + writeJceHead(INT, 5) + writeInt(123123) + }() + writeJceHead(STRUCT_END, 0) + } + + assertEquals( + TestSerializableClassA( + mapOf( + TestSerializableClassB(123, TestSerializableClassC(123123), 9) + to TestSerializableClassC(123123) + ) + ), + Jce.UTF_8.load(TestSerializableClassA.serializer(), input) + ) + } + + @Test + fun testNestedJceStruct() { + @Serializable + data class TestSerializableClassC( + @JceId(5) val value3: Int + ) + + @Serializable + data class TestSerializableClassB( + @JceId(0) val value: Int, + @JceId(123) val nested2: TestSerializableClassC, + @JceId(5) val value5: Int + ) + + @Serializable + data class TestSerializableClassA( + @JceId(0) val value1: Int, + @JceId(4) val notOptional: Int, + @JceId(1) val nestedStruct: TestSerializableClassB, + @JceId(2) val optional: Int = 3 + ) + + val input = buildPacket { + writeJceHead(INT, 0) + writeInt(444) + + writeJceHead(STRUCT_BEGIN, 1); // TestSerializableClassB + { + writeJceHead(INT, 0) + writeInt(123) + + writeJceHead(STRUCT_BEGIN, 123); // TestSerializableClassC + { + writeJceHead(INT, 5) + writeInt(123123) + }() + writeJceHead(STRUCT_END, 0) + + writeJceHead(INT, 5) + writeInt(9) + }() + writeJceHead(STRUCT_END, 0) + + writeJceHead(INT, 4) + writeInt(5) + } + + assertEquals( + TestSerializableClassA( + 444, + 5, + TestSerializableClassB(123, TestSerializableClassC(123123), 9) + ), + Jce.UTF_8.load(TestSerializableClassA.serializer(), input) + ) + } + + @Test + fun testNestedList() { + @Serializable + data class TestSerializableClassA( + // @JceId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3), + @JceId(3) val byteArray2: List> = listOf(listOf(1, 2, 3, 4), listOf(1, 2, 3, 4)) + ) + + val input = buildPacket { + //writeJceHead(SIMPLE_LIST, 0) + //writeJceHead(BYTE, 0) + + //writeJceHead(BYTE, 0) + //byteArrayOf(1, 2, 3).let { + // writeByte(it.size.toByte()) + // writeFully(it) + //} + + writeJceHead(LIST, 3) + + writeJceHead(BYTE, 0) + writeByte(2) + listOf(listOf(1, 2, 3, 4), listOf(1, 2, 3, 4)).forEach { child -> + writeJceHead(LIST, 0) + + writeJceHead(BYTE, 0) + writeByte(child.size.toByte()) + + child.forEach { + writeJceHead(INT, 0) + writeInt(it) + } + } + } + + assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) + } + + @Test + fun testMap() { + @Serializable + data class TestSerializableClassA( + @JceId(0) val byteArray: Map + ) + + val input = buildPacket { + writeJceHead(MAP, 0) + + mapOf(1 to 2, 33 to 44).let { + writeJceHead(BYTE, 0) + writeByte(it.size.toByte()) + + it.forEach { (key, value) -> + writeJceHead(INT, 0) + writeInt(key) + + writeJceHead(INT, 1) + writeInt(value) + } + } + + writeJceHead(SIMPLE_LIST, 3) + writeJceHead(BYTE, 0) + + byteArrayOf(1, 2, 3, 4).let { + writeJceHead(BYTE, 0) + writeByte(it.size.toByte()) + writeFully(it) + } + } + + assertEquals( + TestSerializableClassA(mapOf(1 to 2, 33 to 44)), + Jce.UTF_8.load(TestSerializableClassA.serializer(), input) + ) + } + + @Test + fun testMapStringInt() { + @Serializable + data class TestSerializableClassA( + @JceId(0) val byteArray: Map + ) + + val input = buildPacket { + writeJceHead(MAP, 0) + + mapOf("str1" to 2, "str2" to 44).let { + writeJceHead(BYTE, 0) + writeByte(it.size.toByte()) + + it.forEach { (key, value) -> + writeJceHead(STRING1, 0) + writeByte(key.length.toByte()) + writeStringUtf8(key) + + writeJceHead(INT, 1) + writeInt(value) + } + } + } + + assertEquals( + TestSerializableClassA(mapOf("str1" to 2, "str2" to 44)), + Jce.UTF_8.load(TestSerializableClassA.serializer(), input) + ) + } + + @Test + fun testMapStringByteArray() { + @Serializable + data class TestSerializableClassA( + @JceId(0) val map: Map + ) { + override fun toString(): String { + @Suppress("EXPERIMENTAL_API_USAGE") + return map.entries.joinToString { "${it.key}=${it.value.contentToString()}" } + } + } + + val input = buildPacket { + writeJceHead(MAP, 0) + + mapOf("str1" to byteArrayOf(2, 3, 4), "str2" to byteArrayOf(2, 3, 4)).let { + writeJceHead(BYTE, 0) + writeByte(it.size.toByte()) + + it.forEach { (key, value) -> + writeJceHead(STRING1, 0) + writeByte(key.length.toByte()) + writeFully(key.toByteArray()) + + writeJceHead(SIMPLE_LIST, 1) + writeJceHead(BYTE, 0) + writeJceHead(INT, 0) + writeInt(value.size) + writeFully(value) + } + } + } + + assertEquals( + TestSerializableClassA(mapOf("str1" to byteArrayOf(2, 3, 4), "str2" to byteArrayOf(2, 3, 4))).toString(), + Jce.UTF_8.load(TestSerializableClassA.serializer(), input).toString() + ) + } + + @Test + fun testSimpleByteArray() { + @Serializable + data class TestSerializableClassA( + @JceId(0) val byteArray: ByteArray = byteArrayOf(1, 2, 3), + @JceId(3) val byteArray2: List = listOf(1, 2, 3, 4) + ) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as TestSerializableClassA + + if (!byteArray.contentEquals(other.byteArray)) return false + if (byteArray2 != other.byteArray2) return false + + return true + } + + override fun hashCode(): Int { + var result = byteArray.contentHashCode() + result = 31 * result + byteArray2.hashCode() + return result + } + } + + val input = buildPacket { + writeJceHead(SIMPLE_LIST, 0) + writeJceHead(BYTE, 0) + + byteArrayOf(1, 2, 3).let { + writeJceHead(BYTE, 0) + writeByte(it.size.toByte()) + writeFully(it) + } + + writeJceHead(SIMPLE_LIST, 3) + writeJceHead(BYTE, 0) + + byteArrayOf(1, 2, 3, 4).let { + writeJceHead(BYTE, 0) + writeByte(it.size.toByte()) + writeFully(it) + } + } + + assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) + } + + + @Test + fun testSerializableClassA() { + @Serializable + data class TestSerializableClassA( + @JceId(0) val byte: Byte = 66, + @JceId(1) val short: Short = 123, + @JceId(3) val int: Int = 123456, + @JceId(8) val float: Float = 123f, + @JceId(15) val long: Long = 123456789123456789L, + @JceId(16) val double: Double = 123456.0, + @JceId(17) val boolean: Boolean = true, + @JceId(11111) val nullable: Int? = null, + @JceId(111112) val nullable2: Int? = null, + @JceId(111113) val optional: Int = 123 + ) + + val input = buildPacket { + writeJceHead(BYTE, 0) + writeByte(66) + writeJceHead(SHORT, 1) + writeShort(123) + writeJceHead(INT, 3) + writeInt(123456) + writeJceHead(FLOAT, 8) + writeFloat(123f) + writeJceHead(LONG, 15) + writeLong(123456789123456789L) + writeJceHead(DOUBLE, 16) + writeDouble(123456.0) + writeJceHead(BYTE, 17) + writeByte(1) // boolean + } + + assertEquals(TestSerializableClassA(), Jce.UTF_8.load(TestSerializableClassA.serializer(), input)) + } + + @Test + fun testNoSuchField() { + @Serializable + data class TestSerializableClassA( + @JceId(0) val byte: Byte = 66, + @JceId(1) val short: Short = 123, + @JceId(3) val int: Int + ) + + val input = buildPacket { + writeJceHead(BYTE, 0) + writeByte(66) + writeJceHead(SHORT, 1) + writeShort(123) + } + + assertFailsWith { Jce.UTF_8.load(TestSerializableClassA.serializer(), input) } + } + + @Test + fun testHeadSkip() { + val input = JceInput(buildPacket { + writeJceHead(BYTE, 0) + writeByte(66) + writeJceHead(SHORT, 1) + writeShort(123) + writeJceHead(INT, 3) + writeInt(123456) + writeJceHead(FLOAT, 8) + writeFloat(123f) + writeJceHead(LONG, 15) + writeLong(123456789123456789L) + writeJceHead(DOUBLE, 16) + writeDouble(123456.0) + writeJceHead(BYTE, 17) + writeByte(1) // boolean + }, JceCharset.UTF8) + + assertEquals(123456, input.skipToHeadAndUseIfPossibleOrFail(3) { input.readJceIntValue(it) }) + + assertEquals(true, input.skipToHeadAndUseIfPossibleOrFail(17) { input.readJceBooleanValue(it) }) + + assertFailsWith { + input.skipToHeadAndUseIfPossibleOrFail(18) { + error("test failed") + } + } + } + + @Test + fun testReadPrimitive() { + val input = JceInput(buildPacket { + writeJceHead(BYTE, 0) + writeByte(66) + writeJceHead(SHORT, 1) + writeShort(123) + writeJceHead(INT, 3) + writeInt(123456) + writeJceHead(FLOAT, 8) + writeFloat(123f) + writeJceHead(LONG, 15) + writeLong(123456789123456789L) + writeJceHead(DOUBLE, 16) + writeDouble(123456.0) + writeJceHead(BYTE, 17) + writeByte(1) // boolean + }, JceCharset.UTF8) + assertEquals(66, input.useHead { input.readJceByteValue(it) }) + assertEquals(123, input.useHead { input.readJceShortValue(it) }) + assertEquals(123456, input.useHead { input.readJceIntValue(it) }) + assertEquals(123f, input.useHead { input.readJceFloatValue(it) }) + assertEquals(123456789123456789, input.useHead { input.readJceLongValue(it) }) + assertEquals(123456.0, input.useHead { input.readJceDoubleValue(it) }) + assertEquals(true, input.useHead { input.readJceBooleanValue(it) }) + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt b/mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt new file mode 100644 index 000000000..5290d5639 --- /dev/null +++ b/mirai-core-qqandroid/src/commonTest/kotlin/test/printing.kt @@ -0,0 +1,55 @@ +/* + * 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("NOTHING_TO_INLINE") + +package test + +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.Input +import kotlinx.io.core.readAvailable +import kotlinx.io.core.use +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.DefaultLogger +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiLoggerWithSwitch +import net.mamoe.mirai.utils.io.ByteArrayPool +import net.mamoe.mirai.utils.io.toReadPacket +import net.mamoe.mirai.utils.io.toUHexString +import net.mamoe.mirai.utils.withSwitch +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + + +val DebugLogger: MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(true) + +inline fun ByteArray.debugPrintThis(name: String): ByteArray { + DebugLogger.debug(name + "=" + this.toUHexString()) + return this +} + +@OptIn(ExperimentalContracts::class, MiraiInternalAPI::class) +inline fun Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R { + + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + callsInPlace(onFail, InvocationKind.UNKNOWN) + } + ByteArrayPool.useInstance { + val count = this.readAvailable(it) + try { + return it.toReadPacket(0, count).use(block) + } catch (e: Throwable) { + onFail(it.take(count).toByteArray()).readAvailable(it) + DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count)) + throw e + } + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt index ccc46f90d..a65eee482 100644 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroid.kt @@ -14,6 +14,7 @@ package net.mamoe.mirai.qqandroid import net.mamoe.mirai.Bot import net.mamoe.mirai.BotAccount 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 net.mamoe.mirai.utils.MiraiInternalAPI @@ -21,15 +22,45 @@ import net.mamoe.mirai.utils.MiraiInternalAPI /** * QQ for Android */ -@UseExperimental(MiraiInternalAPI::class) +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn(MiraiInternalAPI::class) actual object QQAndroid : BotFactory { + /** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ + @JvmName("newBot") actual override fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration): Bot { return QQAndroidBot(context, BotAccount(qq, password), configuration) } + /** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ + @JvmName("newBot") fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = QQAndroidBot(BotAccount(qq, password), configuration) + + /** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ + @JvmName("newBot") + actual override fun Bot( + context: Context, + qq: Long, + passwordMd5: ByteArray, + configuration: BotConfiguration + ): Bot = QQAndroidBot(context, BotAccount(qq, passwordMd5), configuration) + + /** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ + @JvmName("newBot") + fun Bot( + qq: Long, + passwordMd5: ByteArray, + configuration: BotConfiguration + ): Bot = QQAndroidBot(BotAccount(qq, passwordMd5), configuration) } /** diff --git a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt index e36fc0aec..041f10968 100644 --- a/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt +++ b/mirai-core-qqandroid/src/jvmMain/kotlin/net/mamoe/mirai/qqandroid/QQAndroidBot.kt @@ -9,20 +9,189 @@ 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.ContextImpl 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) @Suppress("FunctionName") -internal fun QQAndroidBot(account: BotAccount, configuration: BotConfiguration): QQAndroidBot = QQAndroidBot(ContextImpl(), account, configuration) +internal fun QQAndroidBot(account: BotAccount, configuration: BotConfiguration): QQAndroidBot = + QQAndroidBot(ContextImpl(), account, configuration) -@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 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 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 readUTF8LineTo(out: A, limit: Int): Boolean = + this@toKotlinByteReadChannel.readUTF8LineTo(out, limit) + } +} \ No newline at end of file diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt deleted file mode 100644 index 01eae0859..000000000 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/clientToServer.kt +++ /dev/null @@ -1,531 +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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_UNSIGNED_LITERALS") - -package androidPacketTests - -import kotlinx.io.core.* -import net.mamoe.mirai.qqandroid.network.protocol.packet.PacketLogger -import net.mamoe.mirai.utils.cryptor.ECDH -import net.mamoe.mirai.utils.cryptor.contentToString -import net.mamoe.mirai.utils.cryptor.decryptBy -import net.mamoe.mirai.utils.cryptor.initialPublicKey -import net.mamoe.mirai.utils.io.* -import net.mamoe.mirai.utils.io.discardExact -import net.mamoe.mirai.utils.md5 -import kotlin.text.toByteArray - -// sessionTicket = 55 F7 24 8B 04 4E AA A8 98 E6 77 D2 D6 54 A9 B4 43 91 94 A3 0D DA CF 8F E8 94 E0 F4 A2 6B B4 8B 2B 4F 78 8D 21 EE D4 95 A6 F7 A4 3D B5 87 9B 3D -// sessionTicketKey = B6 9D E4 EC 65 38 64 FD C8 3A D8 33 54 35 0C 73 -// randomKey = A4 9A 6A EE 17 5B 7E 3D C0 71 DA 04 1C E1 E4 88 - -// login send 20da22db750806141ef448110800450005aa50ef400080060000c0a8030a71600dd0fe501f908b8c28a908ce7556501001fb487f00000000058c0000000a0200000004000000000e313939343730313032312b87bf2f7c22eb2396f80b887dc6410b85b6f1fc05874d1331d8722ec60d01a47b9ad330945d474934331ec4de12913e927bae5284862329eef2675a122dc83c00407ca3d4e734d772aaae33c4f17b6833a79e12a2ee549a17b08c7e4034874584f3e35e69c55e995e079ed1d0d877be9b1c9a03e62d0f3d714a1506d4a0292a853c6d28b582f5c715f5b6a6d051446b376cd9b55a311b42ba66f20080dc54a429fd240cdc9089534ffc3bcd02ab15f61b86467268e2e743a81f5709c07af6a7b03a90fb1d742988c13fa0b4b946079f9b9b2034fd8a330bc43fe8b2396ec3cac57d6753db28eb3af9e1904e8d4efbe38a354fd1aa71626a8f124498b24d9983d037e5a9f5ad44833df03335e4268cc9489366e793a84f4ed892a7d78345abce6b2888fe081b0317343a7e3bf6dafbe2c1caa0eb956637cf475a16ff1105b10c798a4ee68ea817d6edb0790aba884ced9f638b68cbfeb527d4178769efd533ba032e332f841fc3ede8b50da138470c1118fdb5070eda09215252e7fbca110d6fd4269aca30ed17e642f93caa5b5ef4b1a4abcbb91416e0388f2e625c1a362936360e5935a03a78433a44a56abc936670d44c350a88d0c2e82e092009e6262c1cbd523e6573aa9de5e304b5fc823877fcc4d4c06a08acc62b1b538c9692a70234985347eae2b00956408e2ab1a08a494118f1508364172a0d09eadfcf876855a41894767dd407f75875de1f21f9cdafc6cf8322f62115b721273659995c82987a6e972af56fa0cdd2cde2fe5de5771cafa1db1f70d5c1d70caa9361506533c0495a6f016c0e24fe196ff8a6f288de96255e6e811d6528b26a6c348b99f61b80992033406a05d005c984802ad055360d4e406090b96189c2733436ec3572fe24d6c516214f1a88e46798e2a5244428e61335d6f3f76a8d459715c172016686630d6e20ea6249ea27e4d8d0ae688c79dfc802257614b22f34232eb62d361df629957a05daf743fc8f0e14802035b7534a63628bd10bf128b4ae2ae067a2f6f6f5b62c772ff2afc09a5735e7fd3550886584d99694d001bda4ff945cf0a160b6c21ece0e7fda622ad74d1d15bc6f5173f44d7cbf25d320834e794e7a581350c2330a429a350499335cd1997db163d83de42c3adea77209e4620850900e5dd54a75c393dc89d686ea576fa264d382ead875c30de4462cb32c5f16917d0e2bbad2767a3c4de1a0318a073974f80277b1b9f25af00d0a966511f60de1d4eaa0c576cc27cbc1d910a6dd6c4bfa3ca66bb83cb0f3d791138df8c8afd5d8e8a2635c3d59cbb35d340fb881ad5d57bccbe1042f26ae4dff6b5347938cf5d2cda08f2beef38d666db5333b9168c677acf537b9a37d380ac8c8b4a8da11f5f118316b29f0a465f3616edaa58a0a5360d4b7f503db7685d3b3b702db3b5fb4e1bd8ecd674a1a2d8c85bf0c8c233f3255347991737c2f080f36f3012bfcd9814a205cb5b1ac0d6a99c6f43078d74d4babed46470cde8cf1839f75306d41278020aa94001b9eef595b57a568c03b4f59a5a59d3ac37609d896db7f88aed3a1f3bdc35a7035bcf8249758f41af966e33eb99a33972b5a8f3c1b8e34a27cfb59840a96b944eea462389b5e0c1762d039968bb707874bbbdafa539fbfcfcce58bcfbb913f2cef7a03ace5cdc14c43f195e1494c15cd6af5cc5cb0a13f6e272f65ee3625894c5f950473516c79168944695e295ca3dfd04d1b91ba70eb1c612a570b7bf1ca06c8d83da20c2e442acf5b70b7b3597a802bcf8b024a765f429b964809fcb48f43d727e0606b56fa4545361f743418a042c086f6dbce97ceb5719467dcbbb759e77eebd6c97b9f4ccc6312bbd972215a58ff0a83ff1717430273e11b04751f9beaeca285546ad1d24edc9ff19a9718e4e6eb6ea80e7e16a0518a96059659dc39c50bcbd4aa64f888d3085c7c5899b3a2c2342b598d -// trans emp 1 send 20da22db750806141ef4481108004500038450f2400080060000c0a8030a71600dd0fe501f908b8c2e3508ce7dde50180200465900000000035c0000000a0200000004000000000e31393934373031303231fbb1b5c286bc9eb6a2ae43ce77353dcb0bebf522873dfc23064ea499d060fd11751986d486a6744341c3ff8ee89c30566491a5a449363549f9b917f20e9b19eb04c58d7347e51ec00be55a5e4c2433f4fd981f617726a7f74f66f6b253080103d4754ccd9474a6451123818c94b84a9613875fce9aee86c9f3879df9d0918663ea888389ddb66007827a5bf38c97a7ea6f2ef60468519679c3405444df4a334108f03ca88fdebeb3e3ed39c0b8de6d440469428ceea3fac54ccb4c620d394ec98f94534419f34ec3c2200f6d066cee9d6b3dbc6e46dc313e3863681529f1647bf9d5726747954e3ffa7515105a98bb5a9b17b92a6c56cbcff298d46b653d2f72cbc24165cc0118910aba8c56b1cb6b35b2f7df51f30965bc74cdf422611779e6d82bda537e4790a0acb3b25004fd49cfcae70cc5f52e4c267e1aeb63acf1db34a0f591282024382d99453dee4a75aa6d9e0b69fe42efd1aeb914a4324066aa65037a1c8ca151e562c0bd502f2f5eb80deff7d817ef5cb5a4a03d13f08ce1bfe4485ced084f81376b2fb83f82200725c2a9e5be2f0ebea6b9a68d8ce072c68a62be4eaab170ef94036269267f53f75ed3f6369c80c50ceb9e481c8858e077e16a8d7a80df1406e792a561f635e6a4d5e6662e2422ec88617e350b8686b17ab3c17b6a3b59f9af1519c4c73642e14b9a533073455170daa51fbed6352fa3c957038256079a4395eecc2b6712d0ecdf9a62be9191c2b7cd22dd81c78865ba57626614415f78d8b7812f107acc9110bcff90a376a52e2dd652743770df9eaa9f199bc9e66997fbe121a005c606e9e3855473452379bc4e68f30f3be75f0346c452db79076bd7a47ecc3ad1b8fb2bb796fbf6c3899f1fbc61ee1560d5e9fed4ec157e6e377098e3d7ad43997f342393472e5084b6e4c44be0f2e527f01cb3ea849621ed0108d6109992b08db4d51b13918ca59622f7c0e8389520d79ec96e7818cebd47dd2a700069c32972133cf87083c58571a74c94b2a56c3bbc6f0a94eb95218122e19763deda732536a599138a4bd0b68a59526bba99476c3a5bf065f11b5abe9fd5c74d7bc2b407a362373cb5cf243ae198185b5dc9154d36409153c79097578c8d7c1ae3629dc46c5c9c0302c61c12d05051f82381029e6affe7c65d9b66ec -// GrayUinPro.Check (UniPacket) send 20da22db750806141ef4481108004500018c50f4400080060000c0a8030a71600dd0fe501f908b8c32f508ce7dde5018020044610000000001640000000a0200000004000000000e31393934373031303231f822fc392e93d773a975a2d467d2c40df1021fa5748fd80e8e86af4f4aa9c7745671b903fcb3dea0f314b7e9543b22f02410bd5288fcf358666cb9db4d452cefde2cc9e11b27c7e2ef386a7e8b523af49340e1a9ed10c3a37e6417028f5c019272c7b8e0e1a5af0b27d005c1333777376d960bb41f419842352c2a00e4ede8c642c4f4fd1339d8e81950e9490637cacf42c3ddb5dcb0e987836e77aeb65cf50d6a0867d061b08639f72eafe7b7c5f44240a1e1a9905526bdc6037373bfa20a3fe6d38db3696381831ef1725dfafc5e65b9c1fe77a85080f1a5dfe0c4961d21cd5b70623551b5371f0b4a6d9792d0332b5611cb54e56aa4b99704b34b27a661b7775cc0d16b981c7a7b57283b803b818869d21c91b84ade0ffda282f83bf6619084ef4a17b6301d096211c7bb00768e0d481b11f4907a130f092b4e2fbefdd9570718294c52232eae -// ConfigurationService.ReqGetConfig send 20da22db750806141ef4481108004500017450f7400080060000c0a8030a71600dd0fe501f908b8c345908ce7ff6501801fe444900000000014c0000000a0100000044e0e22a59327abb9ce80cf63be86694210344fab2f2b065d7785a32cacfa4075cd509f49cb37a075ad0685cbd472344bfdef1ede611c0ad81129be9e2d7e476d2000000000e3139393437303130323110192b12a4688c94df8f36df4db7a4f485475e5166166ac2d63858b4d6c0a5748b88373db0353efe12acb0df584dae933d1b6e81f3c19dfeea5e3457db3e82523f71d153dc1e6e14e3f144b4c1fda31aac8321d2d23cad0d4365985e2bcc39e20411a9520dedc8cba4ba1580215e6a27889390a8253aa783315e9ca4dd9f637cd137b4eec24ef8384851b989dfe562e69b9ee519da64abbe9ef25507f42804bd96357a219e62e594f025303dde5901a6b662f704ca3d21e40e2593910315fd3fbb571f513f047ac4b4abf6dea7324459cc6040c6776810bcfd2dd531fc139624394ae11c0143f261ecbbb331e83e1af3 - -// CertifiedAccountSvc.certified_account_read.GetMainPage send 20da22db750806141ef4481108004500025c50fb400080060000c0a8030a71600dd0fe501f908b8c37d908ce913e5018020045310000000002340000000b0100014e74000000000e31393934373031303231c64688d01401061eff81d0ddcdd5a41f2cf87fa1fb2057ef139d96c1e5b27f530e1b7378df9ea59103616c09895434f33523add35ab97703588ea24da0f616cc56a745f76c9071a9408b3d71ff8dbfd7dcfb9b69897c6bf9d42aef2a237f92e0ecf036f9ae20de61d6859d6681d195ccfcaadf6d3eca4211eec7ddb6ee8764ad9061353bf1e1f943a9546a88e4842e2ade0ee6229df968c1d0a119af4bd804af16863152203526dc6057216a676a80c0eb1f678dc2d9d0ab6dd5f6420d67a9889b7559e81a2c1cff31477a9f2dc6ae79c34b6da8564e589e6cb6ed4d201a9e47d85829ec21a5bd34066f8b2bf620dec4ed59dd2268e781cb8d53301e59dccb325a8f21c4e01cd56cf7f06566287afc58a16bd53cb6724f31629a3126a38cb8a29fb58f91fed11d90faacc2607dc853940c9eae2abcb26a6f54d868bbba36bad09a895baaf1915f79ad635a6af00144eac218d0a1664f83a261db7f3500ae85a26deaccc43ee98af0fb2be07b6cdda12931c51fe880e0bfb2f219993e71b4d36f5b22a05648c4ce23d9e767f695e25e6a2a303917d50934f80c1c9dfdfcee585d240269f7145ca4392231b808f8d4836ac56e02d5ece083432ccaf71228ea76a228484f4ec80688a08e452edbb02d033b752c1311bac1132a9bc14e33078b112113d29e305fafa2ac3aaad765420e1e28ff93fbbdf8c94c6d10046f3398d29f06917fcbdcd5c68735ef0e95558312d24df95909f6a7fa4f07 -// x2 - -// OidbSvc.0xcf8 (getShoppingCardInfo)send 20da22db750806141ef4481108004500009450fd400080060000c0a8030a71600dd0fe501f908b8c3b2908ce927e501801fe436900000000006c0000000b0100014e78000000000e31393934373031303231fde67e3ef90dfab4c8706743671f3abf660fc3f77e4efa3fabdeb96e9984107b3f4bf7677b5d34c72e88a6e212af9107e3c52c5f97b7e41c2c3cecfe9301293ab4be504d4d2eca28a816ec514deca805 -// OidbSvc.0x59f (requestIsFirstLogin) send 20da22db750806141ef4481108004500008c50fe400080060000c0a8030a71600dd0fe501f908b8c3b9508ce9366501801fe43610000000000640000000b0100014e7a000000000e313939343730313032319798479335a617d6037b4351bf340716d7095f01a83a19090ced9170a5d2e45578915b186770906f21b623304ee25b7ccb52126e3ab84275d2063f4801b3ea88f4b8fd064be98e75 -// friendlist.GetTroopListReqV2 send 20da22db750806141ef4481108004500011450ff400080060000c0a8030a71600dd0fe501f908b8c3bf908ce963e501801fb43e90000000000ec0000000b0100014e7b000000000e31393934373031303231e92012cde1f02af5088df9ee11e33f90ffc5416dce23cff1193985eb747ea1f453957b000b9f642708e394f590d5c8e7030064ef6f56210330319d4dd4fe21f0463095d74c6d5e21d94fcfb318c58e18d5a83ae548c03827b28c384e9a598f2ec9758ccb313fb825d44b727c10f8929682c5b6d34a8721f06e7ebb6768d39c6e1283b29bbdc15c6f35943fcc26195d1db57ae8de55d96637c7ae4caaa432bcf6768f17a39aa7f49882a86af04876146a2e44c3dcebf3ab106944cf7de19c85111add7b97c8d6bb53f773f11c09525614 -// OidbSvc.0x791_0 (getRedPointInfo) send 20da22db750806141ef448110800450000bc5101400080060000c0a8030a71600dd0fe501f908b8c3ce508ce96a6501801fa43910000000000940000000b0100014e7d000000000e313939343730313032311ca9030fef4676440544c30ee8455143b3d29bf4b96fcb3923e437c30fd64795a325c777e38cd08f173f5dd6814541ca160a2de1ebdb46dcd73386cbf35ee2faae6cdac5c91aa81cf0beca175d7fdee366bacea06859de8b3e163e95d6765256fc523b7e435dc8f73d1a3f5a5c6c793706c88e103a55efa5 -// OidbSvc.0x480_9 (getDetailCardInfo) send 20da22db750806141ef4481108004500008c5102400080060000c0a8030a71600dd0fe501f908b8c3d7908ce97165018020043610000000000640000000b0100014e7e000000000e3139393437303130323183063dc1abd81bf314960d1a00d2a457ff44cb694c4d17d048b888600262a1a957713055cd6e2a68a55d239e1d70d26f9a39bfc61418a8cfbdd96dac2ae1be32e34f94a0271339d8 -// OidbSvc.0x5eb_15 (getHiddenSwitch) send 20da22db750806141ef4481108004500009c5105400080060000c0a8030a71600dd0fe501f908b8c3ddd08ce9e7e501801fe43710000000000740000000b0100014e7f000000000e31393934373031303231e80e3bc84057f0f3cdbfcde52cf82c5c640f69afd3680638b3bbd86bab49ddb8e29e297815f1f41ddfecfe241f76b57f999b02988fe5bb6f92da0693e27e023baea0504b7ca768c541630e636c31a01bdce3df4c27f88906 - -// CliLogSvc.UploadReq send 20da22db750806141ef448110800450001cc5106400080060000c0a8030a71600dd0fe501f908b8c3e5108ce9eee501801fd44a10000000001a40000000b0100014e80000000000e31393934373031303231393e687d1ca6ee2f38f879c89a930bfbd7a9d0d5d6a84e3e76574b272980e8222c35564db3952d09f452bd954248922527ac9aee51a742b3508d50d3794ce81120245bff0a4e3ac74b026fb5444e9e5a5fad8a5c9865e855ede492fa445cc27e48a27f05d81f39e7f9818890717bffeab38df651f21e884cf5a7292c0f94aa3a849cbfdc84bc7e560e371fbbc4d62c1bd134b41f66915272cee5e4094ef47a5641cdb7e19a070a6babd955a02e722b2a5fbad6adc161f58f295d9a6406dd8bd9ac14bfc520e454eb2f0422ae20beaf6789d25ca367cb22fae28488670f5871e4fa7cd8953eb854a4b53cdd8ef0406f5efbd46870afb095efb7b375f956d4f085b6e03c7f7eada6b35d3cdc6e17c2e0169177f6bc24074f1a5fe90ea54cd8af627a00cb78518e6b7f3daab0811fc0872408581f385d25efeecfa4634998ffa81d43868521467262ff08f7fc338fac60499c3452f530bce46272599401fb4cf363ff74e8813c8e12a51b6bfb992e24d3dcf1dfde34af16e6e892f0a3cf146d2d3e444b62432e5e2932 -// CliLogSvc.UploadReq send 20da22db750806141ef448110800450001c45107400080060000c0a8030a71600dd0fe501f908b8c3ff508ce9eee501801fd449900000000019c0000000b0100014e81000000000e3139393437303130323128f6ceffe231f5a913fe9b4c59d24ea84d7dd8a67ef3c802f5eb2705ffeec4e98ddb95ec61e2adb2a52bcf4f744613a956a7af05be228b1dc95d40a0c4e187e79b495946e95bf2ffcabfc550c4f384269f87fea6d550a744a493cc0e11fea05cd7816fcf625b48ee6ed270e13fbacaeaef462a43dd17be5fa587e41e28fa40c083caa7e632cdcdef307404d75c30dce888de061ad715192916ec0b740c5614589b8e06683526042f8b74e896ba1049b71b8deff9354b1b2e83991d7426e2844cc592f511d9d51697361f6ef54370c4043b1bec4d7aa227804dc2b539ef95313b77561faa4d23f7392a2d18f5d796f7768af59d3faa65892169636d9c0e26a7b55be18bcb78719bd0a8ab5fa446f55e515ec5f2d0bfe9e0977ac98fe9af99f0426bc7d65066248d605eefed9b3c42f9d95d578e82515afce2fe239b857619bf64faadee7b10268bd3a079c23a413fffa209fdb9c7d23adab92e4c42fb446add76c24556d2f176f9020183814dfb2fa68861576bfcc41dfdd254d3a63dc8528b95 -// OidbSvc.0x787_11 (getTroopMemberListBy0x787) send 20da22db750806141ef448110800450000a45108400080060000c0a8030a71600dd0fe501f908b8c419108ce9eee501801fd437900000000007c0000000b0100014e84000000000e31393934373031303231de7a3d7936e5679df9be661fdb98fa181243fed121a7c37e0a0a035537b27705ed59f5707bf86495d9b4781418f5c8dc36f561d8b659e2936d4549b55ec6c807650b4e5af7c79fe881ee9cb3df4b4b307748103b3b52eef06aeaf81adb25767c -// OidbSvc.0xaf6_0 (getTroopMemberListForHeadBatch) send 20da22db750806141ef44811080045000094510a400080060000c0a8030a71600dd0fe501f908b8c420d08ce9f86501801fd436900000000006c0000000b0100014e85000000000e313939343730313032311425f3e63a4e0a0454c45b48639abbe10c101b19f0a66f37e774289e87cc3c54def76486925c7291f5d37392120d7e0d30a9caba77f73808b201f49ff2a04a55c5ec502b10a6f861dbffb6eed7324b92 -// OidbSvc.0xdc9 (getHostTroopHonorList) send 20da22db750806141ef448110800450000d4510b400080060000c0a8030a71600dd0fe501f908b8c427908cea04e501801fc43a90000000000ac0000000b0100014e86000000000e31393934373031303231abc2f4d9170210b0504e058282363e45527ce2ec6080f48a99d24ca40171ab146ae451517cc60d98585c746d2d00d434d3b44a9e3c9bcbe0a5ee23d406d8ae9c88b9d51beae9d05a3c22fa67f5a03bea60c17017848d32852b7e6abbe290974f74183d7af2b20d6c43c133b52ec2506f9e752676a40518cf73b922505e1e5497fb58146e526627c8fdd2c8ec4424ff96 -// IncreaseURLSvr.QQHeadUrlReq, IncreaseURLSvr.QQHeadUrlReq send 20da22db750806141ef44811080045000150510c400080060000c0a8030a71600dd0fe501f908b8c432508cea116501801fb44250000000000b40000000b0100014e87000000000e31393934373031303231a3e5c904a29879bfe01d903db3522e51d9d388a6f976387267c78f937ebfb4b800cf847d9af9522bcf8b4e710d905935a904be7e144a8f85db87e784b42bdcc1f19b3dbf307b37d62ba3ee42e6145249161da0a9c608289f139e4f545b9efe40f868a9d7fc06381e59499b04da34e36aac978bb42ad6b93efa55fd63ddc18db1216c68afafc50c954caace2c159cc2ae5cdda25d4bcb5f2b000000740000000b0100014e88000000000e31393934373031303231c571c13b189ca8a129aeecfc734352ff401917c63da218afd73b3f95eaf5121a54331f4ae9534b5c5c5add7307c612fe935cbdfedb775ff1b386adba02b35984aac175015452125c1e93cb333e43c314d53cbb0201ebf0a5 -// OidbSvc.0xc42 send 20da22db750806141ef448110800450004ec510d400080060000c0a8030a71600dd0fe501f908b8c444d08cea3ee5018020047c10000000004c40000000b0100014e89000000000e31393934373031303231e59b7a31945585845fec9d893236808c4868e458601b681c89bdefb2b52d4c2b4f3f3d6e6abe0e0e0fc8df28dccc8733c80a4bb9869cfd7203664227c9c485d1e22d02f7fb24944d70542610a73cdb657fb915b1ded3c1a5a52eece81fddbe4df82302b948c88b51bb4fa1bcc9af6302304ffd89ae1fa47f6b732c84c1deb3d4b1bbd6818359f066be51e868d90bd500bb5b3d65ae0cc11a40c3aa03e3aa1e1144dea014d65d1d1ac82cbf6df2415c4b0629bd07323d61f5735bc768d0bbcc7be7fd66ebd9b65232b5491a7c2b33c1079f0af971c0ff178868361cd0fb40b05e3d421145e43a92696ef394f3fbc9e1ac0c037791aa57ad1c6542195c681eb951e7e59532fc9c1eed68c96e79e720d3057f1d6ae4cf4ef253af35fdb2feadec3bac82091d8146a7022b5f2578e3f126a474e9f1cec9d987b055c9217c003931a5cf03530a0062641e6f4549cedf50ea16c75feb7035a849a3229a3663c135cd8905f17176e878fe29942a55107895041595e5b497ca8796d9489d0682e8823589965240ade202ea0efa9f84f1ebc0e25d33e3e81b3a43d7bdc75005fe8b43d09fe914c6af785ba4ad4cb40cb41f5bc622af6dc1cad1460871b8c7fdec63e08a80536ddf340e907b1eddd8fa90d2323d3e2920c7cd80fd379d633e9f8b8c526f47f9e101d6a1edeccaf15d61c893cc55e9c192cd23b7a417c3175461096146a70a08717a8e654086d8db042b674e72dc54a057a80dd7dd291b85af7d4f7ee0b8dabb8a0e58cafb411f1e501b855a2e6ea6b5a089cb8d066d5d9181d0e16d5389123f96a06d630269efe3727ccc2ca84a4b3dbd1f5d786c3a655e2b3e9311bae60acf3855e55b5e96981295193564ab0172ab8fd6fb4a6c05bff477e7800dcfa6d1e6d2face51219e69987a1a03419ce7d6a43b6216f5f3f02d87b8c9e4faf227f4509427f3b26316f3e996a4a9dae90afc693b3eb359ecd2226087305d77bfe3f6fcbb949061f7a4d1076634144fa1b3f2491ca34d75ee4d3c7a4f4ba17233b7e56bcc8c04044ee5c0d0eed5c53814a6efc0347251e47eaf5981bfc5ccc7b3c2c9e6b2d8b5c1b81f2d79d5134420f5909d0526c5d761b94c0afade2dda634f2d44b911b977630dec1c3f7aa0ed1aea422aebb5c6aef49a9873ce5df751bea10926983c43843fa5adc56077f835e84bc07fd434867e4af0d605c64c729baa77e6a22d5b58d4b9378f518b10f57a61a486a815afb0e3591f85b5c5cdb37f5fba5c89b2638b21c27cc369acd6c995932ead6ceb49fdf6122e2c09b385eb43ffbf2e81acb31057beba0700fb388374ce9fb33794b3991e9b3a18123e732b8d14e5aef01391424200dab855c12b47ade4a2c60b760cd9e3c5400d452fa040ead996a21aad6ce6fac722121f35910b39a3cc8e20543fb2539fe3fe538162f086e1de508f0f9b0cc2414b8af6800ece710f944c95031d2536470ca87083a41641392edbf976413844cd7c7d64a6ed61a2f7ce8e09a6948099d62da1f81b28fcb50424f88b1f0a9b271a7172247ff0313e0dff87ef3b2b4d965852d200e0476fb56aba5a71e5e7a2ad500e62e7f15dc165f0a6b315bffde194822420b846fd18ed2f510eb2610d5d23ccaffbded2c495224c0a215859bf03f0cb1ffc7f3373aff418326c2a98c7a44e8bffe93b -// ConfigPushSvc.GetIpDirect send 20da22db750806141ef44811080045000094510e400080060000c0a8030a71600dd0fe501f908b8c491108cea4b6501801ff436900000000006c0000000b0100014e8a000000000e31393934373031303231831dcd7b8023b2e8434f5897e7a34a13b67bcf4def875c650022a4a6b71417d9151938fe206612ae192f55d174e41aa848a02e51441700440260e2fdaf7882cfc60b2a10946dc2e76c172ddefc309389 -// * friendlist.getFriendGroupList send 20da22db750806141ef448110800450001245111400080060000c0a8030a71600dd0fe501f908b8c497d08ceae5e501801fd43f90000000000fc0000000b0100014ea4000000000e31393934373031303231899eb2e0bc999ad0799e23cd538fcb600850fda949fd86368e3897c1e1b736ce01509901fc190da6c2c0eb00edd851ebad2efac593997488b51ec58d289d3a050ff1a732a616e3b4fffd40b7aca1da2d04dc0c9e1205bcc63c9473d9ed5b4386cf41d4a786f7382f94266b09db3e3993a27bff4685ffb7d9eda7f66fcf592c8c8d593aa8c00e3fae4274371e5c4b4c3c23bdbc4f6a255d1f5b66f4bf2d56798099afbb34507f516d16ae24b344607bd23c899e241048c18b227a6094cbdcb12d2a8a33b443f7a4f26bc9b3f04fd8001daa46302bdeb22bf32bdc72d06c4a7959 -// * StatSvc.GetOnlineStatus send 20da22db750806141ef4481108004500008c5112400080060000c0a8030a71600dd0fe501f908b8c4a7908ceae5e501801fd43610000000000640000000b0100014ea5000000000e31393934373031303231954e34510d4f281e8676c7345c1e8d453c99aeca52c7649053a6230953ac2bca49f6e250394081be6153eceaa531aec179450ec3d671d72251db0c36cff7e1ba3b6de0e6ade53de2 -// * account.RequestQueryQQMobileContactsV3 send 20da22db750806141ef448110800450001445115400080060000c0a8030a71600dd0fe501f908b8c4add08ceba36501801ff441900000000011c0000000b0100014ea9000000000e31393934373031303231a78ed17a376177e537dec3ed98b7e47b51e9be299048820b1bf3ae88d8a18324e6773ef89922a7e7cd46f24d18a6f8aa53472bfeb09000bb7c44ef5f646d8a8f9e0d04accb35183b0216bea7dee1ef8a51207f11a0681fbcfcd09364a21b16bd6974fef7d66c7eca43aea623c499f1c81fb995d11d5490e64f7929861b670e2f24dc975684ea3098f0b96aff986b4e0b6b09a45bf7f2e301fa58ba1b462e755685681c112c3d2ac369d785d56c25c12b1c66bf39396030b374400879c922a7c43d6d36cc40aa8482f079ef48355b9a2562669f5bce3d3ace4f014ce0454b158693f2727833d62d29bdc7884b165b93c57e60d443351e9fff7e093494f0c61918 -// OidbSvc.0x58a (getDiscuss) send 20da22db750806141ef448110800450000945116400080060000c0a8030a71600dd0fe501f908b8c4bf908cebb5e501801fe436900000000006c0000000b0100014eaa000000000e3139393437303130323178d1c8e8c5d15a4dca043edc70fa514b19b92706d26a84f2bdb1b3e478d5ef53748b8d6082dea9a74f1c9f0ab519406c1ef36fcc0502cf201f658ecac1cd1682a34bb1ff42ac64da5f35e3af7fabcc8c -// OidbSvc.0x7c4_0, OidbSvc.0xbe8 send 20da22db750806141ef448110800450001085117400080060000c0a8030a71600dd0fe501f908b8c4c6508cebbe6501801fe43dd00000000006c0000000b0100014eab000000000e31393934373031303231a047827363fff4e4596299c74f6d847be9e8b5c2e4327bdaddffb8dd40e8df0eb76c54a6c7b7a56eddbae7136bb2b076386eb3b51091c46e756d1f9241678b656498da02276578cfb1ebfb6445513f51000000740000000b0100014eac000000000e31393934373031303231bd70b59f03df1144ae0ab45768257763ae622d5517bc20092fffaf4a5a3d55118ef6cd02907467c0eaa4a45309661277166f75e8ec4b75c0ec5fbf313e03b4a667ce26dfb0d148039a4d9ef8be7ad8cdf5e5a810edb60dd4 -// OidbSvc.0x58b_0, CertifiedAccountSvc.certified_account_read.GetFollowList send 20da22db750806141ef448110800450002f05118400080060000c0a8030a71600dd0fe501f908b8c4d4508cebcd6501801fd45c500000000009c0000000b0100014ead000000000e313939343730313032311c087c0ad4a44631d3e9eb9a6ebb4456a003db2e7eaa5e27ffd4c3ee592b8de7b89b9f6a56fc8260e57eef4f3b6616a5255ab830548dd50e9a99cf87011584f3811591c35fc95cadfc364bbeb0ed337063a518aca85fd17c30ab57408389f5c071ed2626ec47b1086478164d0ae0bd0f21222eb7d4b7a5601bc160f99a8f70330000022c0000000b0100014eae000000000e313939343730313032312dd572fad1b71384d3fdb16a1c57f3cbadac7e41ae6de9d13cc2827262e4c5a4bb95829233b72b5f9076b4652c1ffd1ebba874003bcf7e3be86cdc1034ac656efde24be90a49484209c954b78dec531b3d194cc92a297efef340d8273de90a63335bf543109e96a5aa958283161e7e78f50b74b49adb176472ac82fdad36adb68b38421deba0d1c1dcf24f8342bb8d39c1181545a675abb92a3e28431e60a02aaca6c2b4f5ea79c3f4431865444932c9af983f07dd39dd6d81e47f0f59e386e5320051472fe9235f3b98b8850a9335074173730103565ddf81fb57fedb200f328d9ba16d2e52ecf1cc590382087529678501b1ab6b5866afb25b70f0d03b0773f84691e6b94e6d03c3c47f1baa4e17552fafce1bcc90e336ae0d7fca76babcb035378ce191aee9315885197a39301f319857c33cd25a017f6a0480e16030b623547216bafe886f41014f65ae2e6cf3b4c58a4ce96b8f0967c4b0e0be6a9e669c25a06e6b412f115522522af613c6b8fee922d4f3f189d3c2696521afd63fdfc4103bd827a3eb4aba949a6e07424510af29c74e3de4faa1706bf128ac7f9f66306852da2390bb6cb8ed34c885b49d577e22b86222e95142b351dec31690804b98d98baa66b405cb6d649069d9e21254f2f81a6963962955c86e6a74be6c159eb9dce9eff7eb7e0c34f864d826ececbc1e577b4498b403140983fd716ca2b04aa5c23b8c18fe28367bc5ba76081c4aafd3 -// CertifiedAccountSvc.certified_account_read.GetFollowList send 20da22db750806141ef44811080045000274511c400080060000c0a8030a71600dd0fe501f908b8c500d08ceca66501801ff454900000000024c0000000b0100014eaf000000000e313939343730313032318b6198dc33ae901346ecbe82c9821a5e2e7e68d070b2baa3dc8525246a625cb7461bf329a93acc9d20b0221108cd838505d2c0e2c9b944cf7c57eb17b74e74c99c873febfc6e89395b139c033c68d66b731b0ad4d9d7444471b7638628924c3ebb4c2ed326a87b1761d5d5a72043c5e3e538d5797843333867177b5f52445e0b5fa5805cba497e5f0f7da203da3504d74977180e1b86dcdb0c3b7298c4b10d6e53a145c277033e20445a82d0fcc78a1ce597294bf767e43ca42608e7531577d185cc14f91db8f3b71b614f5299ab75c448dd7462a0ce66b1a0992db05ebbab6667ca79ff39d2e9e5f58f73eb2b47cf1896e5560ebe389289b2222687ea8af254b4518ccc6d3468dd03ebce6247e7aac4278ba99210b4c91c49c6f29fd3ecb2653eae174e7bddaf40ef743f8da7605d09d36bca5ef84ee9b75d126db616fbe2b67cdbbb26809d56e97b2e4f6175da52694f0ee640ea1990dc79e5a0c2b266ddc16d0b7bf19818ad7727add2fd2ab379ba1b41763d953d0e21dd26e2f469e8273059f5130355a0976d5a01ca438f3b698b96a2c5394b6ebe381b3818b032ee5226a1a246935a741d39fb84feb917f9680d6c7b2e950e74def6523c318cbbe592904167f2ff8dbaf10616fb2b39a5af68fa19443e874ad2a6bc0639240903fccf8d2eecaacb9404c15ec387faf459f438c229e4a571234403078d270499a2634b13baf5051979ebe31ec8fedb55854e73d58534e51a924c637ee797389de4a27da494dbe7b50c69e1c4857537d3f369752e -// AuthSvr.ThemeAuth send 20da22db750806141ef448110800450000a4511d400080060000c0a8030a71600dd0fe501f908b8c525908ceca66501801ff437900000000007c0000000b0100014eb0000000000e31393934373031303231080fdebf8f489c29fc1aa0611a0ee8dbcf8ada3129852e779569f93003e9af00101b5b5bf3c4a87ec47420f735c83d1029c82729ba3191fac7059dac5122342dc4560637b8ff15bfc84d8e84e8dfbc5dc8de067ae6b902c50c3e5a1915f0753a -// CertifiedAccountSvc.certified_account_read.GetFollowList send 20da22db750806141ef4481108004500027c5120400080060000c0a8030a71600dd0fe501f908b8c52d508ced606501801ff45510000000002540000000b0100014eb1000000000e31393934373031303231379d9ef3c72b17257dadcc13699bb8b027ccdd304c5ec4df57671b991739b43493c60017360ba5f88dd8a8afada19c134c82cf9ae4b5216f2abd4bc30563b981cf22865a55edf3856e46c6a3d7081eed19eb3c4f0a45bfe3120dca2d49e105827873ca33d5c2abcfb89428a5bff03fdb6337d86bff7421ffbc35887803ea9820abb58a0f7ccf607b41e203a8c3902933246a1e3b2f751ff97f8e2cc4661185f8f62f5183306222b8926c00249a80a285d9b43bee57935a825b2b07c1b4850953f04e866006538c8e7202444c25e5d87e6b040de52a7ccfd5cbba56e748b6a1d26d6e14bd4968039ae1c0f3745e7f2d41e1f8b3f46547c0dfcf045cef83bfedc9fc5b59e0adc8ec33d752835f814197e3fd2d5d5d99b20961b33b519e5457c14c84a45d4a5a8cf28ef428b33003a79753c7406181a1ac8de81fdfcac09c6a6265787fc1d27db83d52f5f3676da6589ffc2434465a9363f458b12b56c962eef53d354ed450f0671d5be9df26c6611725840cb5c5bce33ff0bb0ac45cbd1ea735122cb0aa7d489720c31afa83eeef8e2b692e9b83afcfb38700391788335dbc811b43bc018062ff9205273bc8271d2afa958f7f3b7c4077beddc6acb77e1864ff2c614f3af60265e6876a5ecbb95ad78e5f8cf74162a7cf87887db855808181a3cd8d2c363ded3af6105ef8164b408ae7925e5b9311091ceaf8b116be765d29c6d1317c1b4d7febe2e3862ebab3c28eb2ea05587ed09cab27ecaedb38f0e651f2662f605251f90d9b029bd4b617188ab49afe306d1094df0ae0 -// OidbSvc.0x7a2_0 send 20da22db750806141ef448110800450000845123400080060000c0a8030a71600dd0fe501f908b8c552908cedcde50180200435900000000005c0000000b0100014eb6000000000e31393934373031303231d751dc69de793526fd65f55e83c1b8d43435ece7da38acf0b8503d8259484c583dbe89b603a4abc1f1f665d1a92876d711431b5833908d216efd7398286b8adf -// ConfigPushSvc.PushResp, ** StatSvc.register send 20da22db750806141ef448110800450002f85124400080060000c0a8030a71600dd0fe501f908b8c558508cee2605018020045cd0000000000ac0000000b01a4da6eb0000000000e3139393437303130323122465f76192ce454d64e90a36fe651eb96d052893396c002a815851dc04994d16f7a596b06ca02b4743077fb387442f00409806ece8bf2f533a129073db60c8d5a22b9d4224387629e8ce29959a683f505b741aa1a83068239f6df0f97a4e5499c1035092e7466b005307110460211aecd1f11c10cea0cc861007e08882accb0bbaec68cd9c9a83fd19bb5eebda016ff000002240000000a0100000044e0e22a59327abb9ce80cf63be86694210344fab2f2b065d7785a32cacfa4075cd509f49cb37a075ad0685cbd472344bfdef1ede611c0ad81129be9e2d7e476d2000000000e31393934373031303231f4cf6ac405adbbfab64c0905f9f1f4b3236e4b17c34bc8aaefd77b184f012c036978eea06bb7f9e6d443a9d71f6eaf8b08b3c49858269de0405d7acf5076fd3000357cdbced53142200fcd8f87dc2a047ecfdd0d374aa0bb1b5c97b35e59a7cc77f146c930d04be6b70a75be3d71704d1b95794cbc36ef0ffe96533d61521c7b4b676f6b067859c0760ae5689f146b3c1a19668b694a50aa1d561f2feb76b49d507dc530d49007ba08f84297b23290177539d0b2a3ceb0a2ef8a64b65494e574ed78857fc1c93911c7639dce6699d5fde605f432d4ab7dfd6cf8cc5451950b29d79f5e755f90faadf55dfcb3993d9b4fb9479f44e47c1a0702205da298327b0deb180e2976d07be7b6ac0302b128d792200a092e084ca6908fe0c13e142d50f42783009029b3d2da23bd1050a2cc56023f943ce3b164002fa31fdf9624a255455cc77b774223726f36cccb2603240ca528534ee1533eb66872b007ea20bb5e76f87efa35d7dcb4be7e5f410ed7276c09e20f04db63ea4b79fd7d2201ef0ec14b5f9795bd2bd028e58b3ab8d3461a188f113a8b50406e9c3db9f8b2115924faadc9f2707c6715dc2dda73119228079467fbe145cf951cf1948e755aa428f4e478a6ab9708ad39ded4 - - -internal var wtSessionTicketKey = "B6 9D E4 EC 65 38 64 FD C8 3A D8 33 54 35 0C 73".hexToBytes() -internal var tgtgtKey = "D7 71 03 E3 4C E5 8F 6B 05 D8 C7 8C 96 FB FB 23".hexToBytes() -internal var deviceToken = "CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40".hexToBytes() -internal var D2Key = "44 28 6B 35 7A 54 2D 45 45 5D 56 32 44 33 47 49".hexToBytes() -internal var userStKey = "35 29 42 54 78 62 47 68 5E 77 68 54 6B 76 57 5F".hexToBytes() -internal var tgtKey = "44 24 3F 43 3F 21 37 2B 29 44 6E 47 70 3A 4E 3D".hexToBytes() - -internal val t108 = "BD 12 96 6C 83 53 EF DD 06 16 52 16 B8 1B 25 69".hexToBytes() -internal val t10c = "23 7D 2C 7A 3F 4A 41 35 7D 3B 45 51 6D 3D 2A 56".hexToBytes() -internal val t163 = "2C 7A 7B 23 4E 24 3F 24 24 47 62 6B 69 2E 47 50".hexToBytes() - - -var ecdhPrivateKeyS = "97a52992cb7a2110413629af94a3c249c68a3b731510caa8" - -internal val shareKeyCalculatedByConstPubKey - by lazy { - ECDH.calculateShareKey( - loadPrivateKey(ecdhPrivateKeyS), - initialPublicKey - ) - } - -var passwordMd5: ByteArray = byteArrayOf() -var uin: Long = 0L - -fun main() { - val data = """ -20da22db750806141ef448110800450000285129400080060000c0a8030a71600dd0fe501f908b8c5bf508cef1de5010020042fd0000 - - - - """.trimIndent() - .trim().split("\n").map { - val bytes = it.trim().autoHexToBytes() - if (bytes[0].toInt() == 0) { - bytes - } else bytes.dropTCPHead() - }.flatMap { it.toList() }.toByteArray() - data.read { decodeMultiClientToServerPackets() } -} - -/** - * 顶层方法. TCP 切掉头后直接来这里 - */ -fun ByteReadPacket.decodeMultiClientToServerPackets() { - println("=======================处理客户端到服务器=======================") - var count = 0 - while (remaining != 0L) { - readBytes((readUInt() - 4u).toInt()).toReadPacket().runCatching { analysisOneFullPacket() }.exceptionOrNull()?.printStackTrace() - count++ - if (remaining != 0L) { - println() - println() - println() - println() - println() - } else DebugLogger.info("=======================共有 $count 个包=======================") - } - println() -} - -fun Map.printTLVMap(name: String = "", keyLength: Int = 2) = - DebugLogger.debug("TLVMap $name= " + this.mapValues { (_, value) -> value.toUHexString() }.mapKeys { - when (keyLength) { - 1 -> it.key.toUByte().contentToString() - 2 -> it.key.toUShort().contentToString() - 4 -> it.key.toUInt().contentToString() - else -> error("Expecting 1, 2 or 4 for keyLength") - } - }.entries.joinToString(prefix = "{", postfix = "}", separator = "\n")) - -fun ByteReadPacket.analysisOneFullPacket(): ByteReadPacket = debugIfFail("Failed", { buildPacket { writeInt(it.size + 4); writeFully(it) } }) { - val flag1 = readInt() - print("flag1=" + flag1.contentToString() + ", ") - val flag2 = readByte().toInt() - print("flag2=$flag2" + ", ") - if (flag1 == 0x0B) { - if (flag2 == 1) { - print("sequenceId = " + readInt().toUHexString() + ", ") - } else { - print("extra data=" + readBytes(readInt() - 4).toUHexString() + ", ") - } - } else { - //if (flag2 == 1) { - val loginExtraData = readBytes(readInt() - 4) - loginExtraData.debugPrintThis("loginExtraData") - // } else { - // this.debugPrint() - // error("未知 flag2") - // } - } - - print("flag3=" + readByte().toUHexString() + ", ") - readString(readInt() - 4) - - print("// 解密 body") - val encrypted = readBytes() - - val decrypted = encrypted.tryDecryptOrNull() - if (decrypted == null) { - println(", cannot decrypt: ${encrypted.toUHexString()}") - error("cannot decrypt: ${encrypted.toUHexString()}") - } else { - decrypted.toReadPacket().debugPrintThis("outer body decrypted").apply { - when (flag1) { - 0x0A -> decodeSso() - 0x0B -> decodeUni() - else -> error("unknown flag1: $flag1") - } - - when (flag2) { - - 2 -> { - - this.debugPrintThis("Oicq Request").apply { - /* - byte 2 // head flag - short 27 + 2 + remaining.length - ushort client.protocolVersion // const 8001 - ushort 0x0001 // const0 - uint client.uin - byte 3 // const1 - ubyte encryptMethod.value // [EncryptMethod] - byte 0 // const2 - int 2 // const3 - int client.appClientVersion - int 0 // const4 - */ - discardExact(3) - readShort().toInt().takeIf { it != 8001 }?.let { - println("这个包不是 oicqRequest") - return@debugIfFail this - //println(" got new protocolVersion=$it") - } - val commandId = readUShort().toInt() - println(" commandId=0x${commandId.toShort().toUHexString()}") - readUShort().toInt().takeIf { it != 1 }?.let { - println(" got new const0=$it") - } - println(" uin=${readUInt()}") - readByte().toInt().takeIf { it != 3 }?.let { - println(" got new const1=$it") - } - val encryptionMethod = readUByte().toInt() - readByte().toInt().takeIf { it != 0 }?.let { - println(" got new const2=$it") - } - readInt().takeIf { it != 2 }?.let { - println(" got new const3=$it") - } - readInt().takeIf { it != 0 }?.let { - println(" got new appClientVersion=$it") - } - readInt().takeIf { it != 0 }?.let { - println(" got new const4=$it") - } - - - discardExact(1) - discardExact(1) - val randomKey = readBytes(16) - println("randomKey= ${randomKey.toUHexString()}") - readUShort().toInt().takeIf { it != 258 }?.let { - println(" got new const in ECDH head(originally=258)=$it") - } - val publicKey = readBytes(readShort().toInt()) - println("ecdh publicKey=" + publicKey.toUHexString()) - - - val encrypt = when (encryptionMethod) { - 135, 7 -> { - ECDH.calculateShareKey( - loadPrivateKey(ecdhPrivateKeyS), - //"04cb366698561e936e80c157e074cab13b0bb68ddeb2824548a1b18dd4fb6122afe12fe48c5266d8d7269d7651a8eb6fe7".chunkedHexToBytes().adjustToPublicKey() // QQ: 04cb366698561e936e80c157e074cab13b0bb68ddeb2824548a1b18dd4fb6122afe12fe48c5266d8d7269d7651a8eb6fe7 - ECDH.constructPublicKey("30 46 30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04 00 1F 03 32 00".hexToBytes() + publicKey) - ) - } - - 69 -> { - error("encryptionMethod 69") - } - else -> error("unknown encryptionMethod=$encryptionMethod") - } - - val encryptedBody = readBytes((remaining - 1).toInt()) - - @Suppress("NAME_SHADOWING") - val decrypted = kotlin.runCatching { - encryptedBody.decryptBy(encrypt).also { println("first by calculatedShareKey or sessionKey(method=7)") } - }.getOrElse { - encryptedBody.decryptBy(shareKeyCalculatedByConstPubKey).also { println("first by shareKeyCalculatedByConstPubKey") } - }.let { firstDecrypted -> - runCatching { - firstDecrypted.decryptBy(encrypt).also { println("second by calculatedShareKey") } - }.getOrElse { - kotlin.runCatching { - firstDecrypted.decryptBy(shareKeyCalculatedByConstPubKey) - }.getOrDefault(firstDecrypted) - } - } - - PacketLogger.info("Real body=" + decrypted.toUHexString()) - decrypted.toReadPacket().apply { - if (commandId == 0x0810) { - DebugLogger.info("发送 login!! 正在获取 tgtgtKey") - try { - discardExact(4) - val tlvMap = readTLVMap() - tlvMap.printTLVMap() - tlvMap[0x106] - ?.also { DebugLogger.info("找到了 0x106") } - ?.decryptBy(md5(passwordMd5 + ByteArray(4) + uin.toInt().toByteArray())) - ?.read { - discardExact(2 + 4 * 4 + 8 + 4 + 4 + 1 + 16) - tgtgtKey = readBytes(16) - DebugLogger.info("获取 tgtgtKey=${tgtgtKey.toUHexString()}") - } ?: DebugLogger.info("找不到 0x106") - } catch (e: Exception) { - e.printStackTrace() - } - } - - } - } - } - else -> { - this.debugPrintThis("uni packet") - } - } - } - } - -} - -fun ByteReadPacket.decodeUni() { - - // 00 00 00 C7 A4 DA 6F A2 20 02 ED BD 20 02 ED BD 01 00 00 00 00 00 00 00 00 00 01 00 00 00 00 4C B8 12 0D E1 DA 19 AF D3 EB 36 76 BD 42 08 F6 DC A5 35 69 C0 8F F2 75 28 B4 CE 09 C9 B7 86 E3 5A 14 D1 0D CA 5D D4 CB 16 77 8B 32 8D 81 3B 3F D9 52 13 77 03 D3 F7 0E CD 7B 21 95 D2 59 CE 0C 31 D6 F1 38 2A FA 82 AD 60 00 00 00 1A 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 50 75 73 68 52 65 73 70 00 00 00 08 02 B0 5B 8B 00 00 00 13 38 35 38 34 31 34 33 36 39 32 31 31 39 39 33 00 00 00 04 00 22 7C 34 35 34 30 30 31 32 32 38 34 33 37 35 39 30 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 00 00 00 04 00 00 00 5B 10 03 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 08 50 75 73 68 52 65 73 70 7D 00 00 1A 08 00 01 06 08 50 75 73 68 52 65 73 70 1D 00 00 09 0A 10 02 22 14 DA 6F A3 0B 8C 98 0C A8 0C - - // 00 00 00 2A 00 00 00 1A 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 50 75 73 68 52 65 73 70 00 00 00 08 02 B0 5B 8B 00 00 00 04 - // 00 00 00 5B 10 03 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 08 50 75 73 68 52 65 73 70 7D 00 00 1A 08 00 01 06 08 50 75 73 68 52 65 73 70 1D 00 00 09 0A 10 01 22 14 DA 6E B1 0B 8C 98 0C A8 0C - println("// 尝试解 Uni") - println("// head") - //return - readBytes(readInt() - 4).debugPrintThis("head").toReadPacket().apply { - val commandName = readString(readInt() - 4).also { PacketLogger.warning("commandName=$it") } - println(commandName) - println(" unknown4Bytes=" + readBytes(readInt() - 4).toUHexString()) - // 00 00 00 1A 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 50 75 73 68 52 65 73 70 - // 00 00 00 08 02 B0 5B 8B - // 00 00 00 04 - println(" extraData=" + readBytes(readInt() - 4).toUHexString()) - } - readBytes(readInt() - 4).debugPrintThis("Real body").read { - // real body - //10 03 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 08 50 75 73 68 52 65 73 70 7D 00 00 1A 08 00 01 06 08 50 75 73 68 52 65 73 70 1D 00 00 09 0A 10 01 22 14 DA 6E B1 0B 8C 98 0C A8 0C - - } -} - -fun ByteReadPacket.decodeSso() { - // 00 00 02 24 - // 00 00 00 0A 01 00 00 00 44 E0 E2 2A 59 32 7A BB 9C E8 0C F6 3B E8 66 94 21 03 44 FA B2 F2 B0 65 D7 78 5A 32 CA CF A4 07 5C D5 09 F4 9C B3 7A 07 5A D0 68 5C BD 47 23 44 BF DE F1 ED E6 11 C0 AD 81 12 9B E9 E2 D7 E4 76 D2 00 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 F4 CF 6A C4 05 AD BB FA B6 4C 09 05 F9 F1 F4 B3 23 6E 4B 17 C3 4B C8 AA EF D7 7B 18 4F 01 2C 03 69 78 EE A0 6B B7 F9 E6 D4 43 A9 D7 1F 6E AF 8B 08 B3 C4 98 58 26 9D E0 40 5D 7A CF 50 76 FD 30 00 35 7C DB CE D5 31 42 20 0F CD 8F 87 DC 2A 04 7E CF DD 0D 37 4A A0 BB 1B 5C 97 B3 5E 59 A7 CC 77 F1 46 C9 30 D0 4B E6 B7 0A 75 BE 3D 71 70 4D 1B 95 79 4C BC 36 EF 0F FE 96 53 3D 61 52 1C 7B 4B 67 6F 6B 06 78 59 C0 76 0A E5 68 9F 14 6B 3C 1A 19 66 8B 69 4A 50 AA 1D 56 1F 2F EB 76 B4 9D 50 7D C5 30 D4 90 07 BA 08 F8 42 97 B2 32 90 17 75 39 D0 B2 A3 CE B0 A2 EF 8A 64 B6 54 94 E5 74 ED 78 85 7F C1 C9 39 11 C7 63 9D CE 66 99 D5 FD E6 05 F4 32 D4 AB 7D FD 6C F8 CC 54 51 95 0B 29 D7 9F 5E 75 5F 90 FA AD F5 5D FC B3 99 3D 9B 4F B9 47 9F 44 E4 7C 1A 07 02 20 5D A2 98 32 7B 0D EB 18 0E 29 76 D0 7B E7 B6 AC 03 02 B1 28 D7 92 20 0A 09 2E 08 4C A6 90 8F E0 C1 3E 14 2D 50 F4 27 83 00 90 29 B3 D2 DA 23 BD 10 50 A2 CC 56 02 3F 94 3C E3 B1 64 00 2F A3 1F DF 96 24 A2 55 45 5C C7 7B 77 42 23 72 6F 36 CC CB 26 03 24 0C A5 28 53 4E E1 53 3E B6 68 72 B0 07 EA 20 BB 5E 76 F8 7E FA 35 D7 DC B4 BE 7E 5F 41 0E D7 27 6C 09 E2 0F 04 DB 63 EA 4B 79 FD 7D 22 01 EF 0E C1 4B 5F 97 95 BD 2B D0 28 E5 8B 3A B8 D3 46 1A 18 8F 11 3A 8B 50 40 6E 9C 3D B9 F8 B2 11 59 24 FA AD C9 F2 70 7C 67 15 DC 2D DA 73 11 92 28 07 94 67 FB E1 45 CF 95 1C F1 94 8E 75 5A A4 28 F4 E4 78 A6 AB 97 08 AD 39 DE D4 - - // 00 00 00 C1 - // 00 01 4E B9 //sequence - // 20 02 ED BD - // 20 02 ED BD - // 01 00 00 00 00 00 00 00 00 00 01 00 - // - // 00 00 00 4C // extra - // B8 12 0D E1 DA 19 AF D3 EB 36 76 BD 42 08 F6 DC A5 35 69 C0 8F F2 75 28 B4 CE 09 C9 B7 86 E3 5A 14 D1 0D CA 5D D4 CB 16 77 8B 32 8D 81 3B 3F D9 52 13 77 03 D3 F7 0E CD 7B 21 95 D2 59 CE 0C 31 D6 F1 38 2A FA 82 AD 60 - // - // 00 00 00 14 //cmd - // 53 74 61 74 53 76 63 2E 72 65 67 69 73 74 65 72 - // - // 00 00 00 08 - // 02 B0 5B 8B - // - // 00 00 00 13 - // 38 35 38 34 31 34 33 36 39 32 31 31 39 39 33 - // - // 00 00 00 04 - // - // 00 22 7C 34 35 34 30 30 31 32 32 38 34 33 37 35 39 30 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 - // - // 00 00 00 04 - - // 00 00 00 FD 10 03 2C 3C 4C 56 0B 50 75 73 68 53 65 72 76 69 63 65 66 0E 53 76 63 52 65 71 52 65 67 69 73 74 65 72 7D 00 01 00 CD 08 00 01 06 0E 53 76 63 52 65 71 52 65 67 69 73 74 65 72 1D 00 01 00 B5 0A 02 76 E4 B8 DD 10 07 2C 36 00 40 0B 5C 6C 7C 8C 9C AC B0 19 C0 01 D6 00 EC FD 10 00 00 10 BB 95 0A A0 8F AB 2E 55 38 39 FF 6D 90 40 B3 48 F1 11 08 04 FC 12 F6 13 0D 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 F6 14 0D 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 F6 15 05 37 2E 31 2E 31 F0 16 01 F1 17 06 0F FC 18 FC 1A F3 1B 00 00 00 00 D0 0D 60 71 F6 1C 00 FC 1D F6 1E 0A 5B 75 5D 4F 6E 65 50 6C 75 73 F6 1F 14 3F 4F 4E 45 50 4C 55 53 20 41 35 30 30 30 5F 32 33 5F 31 37 F6 20 00 FD 21 00 00 0D 0A 04 08 2E 10 00 0A 05 08 9B 02 10 00 FC 22 FC 24 0B 8C 98 0C A8 0C - - - println("// 尝试解 SSO") - println("// head") - discardExact(4) - (" sequenceId=" + readUInt()) - println(" subAppId=" + readUInt()) - println(" subAppId2=" + readUInt()) - println(" unknownHex=" + readBytes(12).toUHexString()) - println(" extraData=" + readBytes(readInt() - 4).toUHexString()) - val commandName = readBytes(readInt() - 4).encodeToString() - PacketLogger.warning(" commandName=$commandName") - (" unknown4Bytes=" + readBytes(readInt() - 4).toUHexString()) - (" imei=" + readBytes(readInt() - 4).toUHexString()) - (" 0 bytes=" + readBytes(readInt() - 4).toUHexString()) - (" ksid=" + readBytes(readShort() - 2).toUHexString()) - (" 0 bytes=" + readBytes(readInt() - 4).toUHexString()) - - println() - discardExact(4) - println("// body(maybe OicqRequest)") -} - -val keys: Map - get() = mapOf( - "16 zero" to ByteArray(16), - "wtSessionTicketKey" to wtSessionTicketKey, - "D2 key" to D2Key, - "tgtgtKey" to tgtgtKey, - "tgtKey" to tgtKey, - "userStKey" to userStKey, - "deviceToken" to deviceToken, - "shareKeyCalculatedByConstPubKey" to shareKeyCalculatedByConstPubKey, - "t108" to t108, - "t10c" to t10c, - "t163" to t163 - ) - -fun ByteArray.tryDecrypt(): ByteArray { - return this.tryDecryptOrNull() ?: error("Cannot decrypt. Encrypted data=" + this.toUHexString()) -} - -fun ByteArray.tryDecryptOrNull(): ByteArray? { - keys.forEach { (key, value) -> - kotlin.runCatching { - return decryptBy(value).also { println("outer by $key") } - } - } - return null -} - -fun main1() { - val toUHexString = - "20da22db750806141ef4481108004500012c525d400080060000c0a8030a71600dd0fe501f908b8d83b908d5d6de501803fe44010000000001040000000b01000150ce000000000e31393934373031303231d2d5378a3c47b184e294b2afbf14704d7317bb38be8273dfa287e00a7aba8a8171771de1717fb7c1661d8c3d414f51096ab7b77b8828a65aab7e40259bc8359cc6e23a5f941d700fd7894d416b7a29a270773df81d3265d7d8d16d13429c0c72db48954b66efb9e6e4c13b2c36b0d73fe285c82a8c650f0b1cf1a7c7e11f0c32f50814aa5a43cd8ea88214249763f053794e338d5f1cf81c893b3944cca7635ffcbf8742892da5f4bcb2694954ddaee63fa2a298dc3bd4a22710f2064293c5304ad4faf5baa5b24b56455994ca4c4b1755c723aff08be5dc3a1bb6a72e10bb9ae77054baf54b7091" - .chunkedHexToBytes().drop(16 * 3 + 6).toByteArray().toUHexString() - - println(toUHexString) - println() - println() - - /* - 00 00 01 23 remaining + 4 - - 00 00 00 0A - 02 00 00 00 - 04 00 00 00 - 00 05 - 00 05 - 30 40 3C 5C D4 C3 65 C7 7F A4 40 A3 4F 88 7F D8 56 1C F1 12 EE 3E FC 7E 51 94 F6 D9 2E 01 2D CB BB 1D 7E 3A 01 0E AB 97 FB 55 20 2C 05 82 6D 70 87 33 F0 97 6F B3 04 DC 90 EC C1 D3 C6 C3 66 D8 26 1A B2 08 0B 89 0F 25 AB 8B 91 5C B8 C9 FF A1 DE 43 0F D2 F4 E5 F6 C0 1D DE 65 0B 72 1D 24 D8 7E C0 A6 31 64 71 1C C2 7D 39 93 6B 86 9F 62 2B 76 58 6C 49 5D 60 0B A6 E7 90 AB CB A8 72 E5 3F 6F 25 B2 AD A6 C8 C6 B7 B5 2D 90 19 71 A0 46 57 F4 BD 96 7D E2 EF 86 DA BE B8 F9 EB DA BB D0 B6 F0 73 1C 27 14 DB 3A 66 BF F9 68 CA 4A 7B 4A D2 DF 66 C8 B4 C5 56 93 72 22 D0 38 FD CA 61 74 31 6A C5 3D 0B 3F E2 92 6A 84 16 B3 E5 86 AD D3 87 7C 32 3E 86 DA B4 E7 69 A0 AF A3 C7 97 DF 90 DC 9A 5A 46 5F DA 32 2A 15 21 C6 A0 8C 8D DA AE B2 4D 49 0E 07 05 5F 12 03 1D 0F 5B 53 6A 8E F0 29 78 41 BD 19 AC BB 92 44 D7 2F 7A FB A9 46 39 AF 69 - */ - - // first (cli log) - // 00 00 01 23 - // 00 00 00 0A - // 02 00 00 00 - // 04 00 - // 00 00 00 05 - // 30 - // - // 40 3C 5C D4 C3 65 C7 7F A4 40 A3 4F 88 7F D8 56 1C F1 12 EE 3E FC 7E 51 94 F6 D9 2E 01 2D CB BB 1D 7E 3A 01 0E AB 97 FB 55 20 2C 05 82 6D 70 87 33 F0 97 6F B3 04 DC 90 EC C1 D3 C6 C3 66 D8 26 1A B2 08 0B 89 0F 25 AB 8B 91 5C B8 C9 FF A1 DE 43 0F D2 F4 E5 F6 C0 1D DE 65 0B 72 1D 24 D8 7E C0 A6 31 64 71 1C C2 7D 39 93 6B 86 9F 62 2B 76 58 6C 49 5D 60 0B A6 E7 90 AB CB A8 72 E5 3F 6F 25 B2 AD A6 C8 C6 B7 B5 2D 90 19 71 A0 46 57 F4 BD 96 7D E2 EF 86 DA BE B8 F9 EB DA BB D0 B6 F0 73 1C 27 14 DB 3A 66 BF F9 68 CA 4A 7B 4A D2 DF 66 C8 B4 C5 56 93 72 22 D0 38 FD CA 61 74 31 6A C5 3D 0B 3F E2 92 6A 84 16 B3 E5 86 AD D3 87 7C 32 3E 86 DA B4 E7 69 A0 AF A3 C7 97 DF 90 DC 9A 5A 46 5F DA 32 2A 15 21 C6 A0 8C 8D DA AE B2 4D 49 0E 07 05 5F 12 03 1D 0F 5B 53 6A 8E F0 29 78 41 BD 19 AC BB 92 44 D7 2F 7A FB A9 46 39 AF 69 - // - - - // second, cli log - - - // third, longest - - - // full trans_emp packet: - - /* - 00 00 03 5C // =860 - 00 00 00 0A - 02 - 00 00 00 04 extra data length - 00 - - 00 00 00 0E - 31 39 39 34 37 30 31 30 32 31 // uin - - // encrypted by 16 zero - FB B1 B5 C2 86 BC 9E B6 A2 AE 43 CE 77 35 3D CB 0B EB F5 22 87 3D FC 23 06 4E A4 99 D0 60 FD 11 75 19 86 D4 86 A6 74 43 41 C3 FF 8E E8 9C 30 56 64 91 A5 A4 49 36 35 49 F9 B9 17 F2 0E 9B 19 EB 04 C5 8D 73 47 E5 1E C0 0B E5 5A 5E 4C 24 33 F4 FD 98 1F 61 77 26 A7 F7 4F 66 F6 B2 53 08 01 03 D4 75 4C CD 94 74 A6 45 11 23 81 8C 94 B8 4A 96 13 87 5F CE 9A EE 86 C9 F3 87 9D F9 D0 91 86 63 EA 88 83 89 DD B6 60 07 82 7A 5B F3 8C 97 A7 EA 6F 2E F6 04 68 51 96 79 C3 40 54 44 DF 4A 33 41 08 F0 3C A8 8F DE BE B3 E3 ED 39 C0 B8 DE 6D 44 04 69 42 8C EE A3 FA C5 4C CB 4C 62 0D 39 4E C9 8F 94 53 44 19 F3 4E C3 C2 20 0F 6D 06 6C EE 9D 6B 3D BC 6E 46 DC 31 3E 38 63 68 15 29 F1 64 7B F9 D5 72 67 47 95 4E 3F FA 75 15 10 5A 98 BB 5A 9B 17 B9 2A 6C 56 CB CF F2 98 D4 6B 65 3D 2F 72 CB C2 41 65 CC 01 18 91 0A BA 8C 56 B1 CB 6B 35 B2 F7 DF 51 F3 09 65 BC 74 CD F4 22 61 17 79 E6 D8 2B DA 53 7E 47 90 A0 AC B3 B2 50 04 FD 49 CF CA E7 0C C5 F5 2E 4C 26 7E 1A EB 63 AC F1 DB 34 A0 F5 91 28 20 24 38 2D 99 45 3D EE 4A 75 AA 6D 9E 0B 69 FE 42 EF D1 AE B9 14 A4 32 40 66 AA 65 03 7A 1C 8C A1 51 E5 62 C0 BD 50 2F 2F 5E B8 0D EF F7 D8 17 EF 5C B5 A4 A0 3D 13 F0 8C E1 BF E4 48 5C ED 08 4F 81 37 6B 2F B8 3F 82 20 07 25 C2 A9 E5 BE 2F 0E BE A6 B9 A6 8D 8C E0 72 C6 8A 62 BE 4E AA B1 70 EF 94 03 62 69 26 7F 53 F7 5E D3 F6 36 9C 80 C5 0C EB 9E 48 1C 88 58 E0 77 E1 6A 8D 7A 80 DF 14 06 E7 92 A5 61 F6 35 E6 A4 D5 E6 66 2E 24 22 EC 88 61 7E 35 0B 86 86 B1 7A B3 C1 7B 6A 3B 59 F9 AF 15 19 C4 C7 36 42 E1 4B 9A 53 30 73 45 51 70 DA A5 1F BE D6 35 2F A3 C9 57 03 82 56 07 9A 43 95 EE CC 2B 67 12 D0 EC DF 9A 62 BE 91 91 C2 B7 CD 22 DD 81 C7 88 65 BA 57 62 66 14 41 5F 78 D8 B7 81 2F 10 7A CC 91 10 BC FF 90 A3 76 A5 2E 2D D6 52 74 37 70 DF 9E AA 9F 19 9B C9 E6 69 97 FB E1 21 A0 05 C6 06 E9 E3 85 54 73 45 23 79 BC 4E 68 F3 0F 3B E7 5F 03 46 C4 52 DB 79 07 6B D7 A4 7E CC 3A D1 B8 FB 2B B7 96 FB F6 C3 89 9F 1F BC 61 EE 15 60 D5 E9 FE D4 EC 15 7E 6E 37 70 98 E3 D7 AD 43 99 7F 34 23 93 47 2E 50 84 B6 E4 C4 4B E0 F2 E5 27 F0 1C B3 EA 84 96 21 ED 01 08 D6 10 99 92 B0 8D B4 D5 1B 13 91 8C A5 96 22 F7 C0 E8 38 95 20 D7 9E C9 6E 78 18 CE BD 47 DD 2A 70 00 69 C3 29 72 13 3C F8 70 83 C5 85 71 A7 4C 94 B2 A5 6C 3B BC 6F 0A 94 EB 95 21 81 22 E1 97 63 DE DA 73 25 36 A5 99 13 8A 4B D0 B6 8A 59 52 6B BA 99 47 6C 3A 5B F0 65 F1 1B 5A BE 9F D5 C7 4D 7B C2 B4 07 A3 62 37 3C B5 CF 24 3A E1 98 18 5B 5D C9 15 4D 36 40 91 53 C7 90 97 57 8C 8D 7C 1A E3 62 9D C4 6C 5C 9C 03 02 C6 1C 12 D0 50 51 F8 23 81 02 9E 6A FF E7 C6 5D 9B 66 EC - */ - // decrypted: - - // 00 00 00 C2 - // 00 01 4E 66 // =85606 - // - // 20 02 ED BD // =537062845 - // 20 02 ED BD - // - // 01 00 00 00 00 00 00 00 00 00 01 00 - // - // 00 00 00 4C //72+4, unknown - // B8 12 0D E1 - // DA 19 AF D3 - // EB 36 76 BD - // 42 08 F6 DC A5 35 69 C0 8F F2 75 28 B4 CE 09 C9 B7 86 E3 5A 14 D1 0D CA 5D D4 CB 16 77 8B 32 8D 81 3B 3F D9 52 13 77 03 D3 F7 0E CD 7B 21 95 D2 59 CE 0C 31 D6 F1 38 2A FA 82 AD 60 - // - // 00 00 00 15 // 17+4, command - // 77 74 6C 6F 67 69 6E 2E 74 72 61 6E 73 5F 65 6D 70 // wtlogin.trans_emp - // - // 00 00 00 08 // 4+4 - // 02 B0 5B 8B // unknown, =45112203 - // - // 00 00 00 13 // 15+4, imei: - // 38 35 38 34 31 34 33 36 39 32 31 31 39 39 33 - // - // 00 00 00 04 - // - // 00 22 // 32+2, ksid: - // 7C 34 35 34 30 30 31 32 32 38 34 33 37 35 39 30 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 - // - // 00 00 00 04 - // - // 00 00 02 70 // remaining size=620+4 - // 02 - // 02 6C // 27+2+body.size = 620 = 27+2+591 - // 1F 41 // 8001 - // 08 12 // commandId 2066 - // 00 01 // const? - // 76 E4 B8 DD // accountId=1994701021 - // 03 - // 07 // EncryptMethod - // 00 - // 00 00 00 02 - // 00 00 00 00 - // 00 00 00 00 // const - // - // // OutgoingPacket.body: ECDH encrypted - // 01 - // 01 - // // private key: len=16 - // A4 9A 6A EE 17 5B 7E 3D C0 71 DA 04 1C E1 E4 88 - // 01 02 // =258 - // // pub key: len=49 - // [00 31] 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 - // E8 6F F6 C1 D9 8B 9C C1 B2 99 A9 53 68 80 E0 4A 34 F9 C2 F7 6D A5 4E E6 25 F1 31 A7 16 46 6D 2A E5 14 2B 64 8D EA 29 15 19 48 69 34 C4 90 D1 50 9A A6 3F 58 69 94 B1 E2 1E E7 C5 D9 53 5B 6B 71 9F 20 9D 2D 02 B3 0D DF B0 F1 0E 03 1E 2C E8 5F F6 28 D6 97 FB A9 45 C6 E1 FF 79 84 C7 7E 42 79 81 BB 48 48 AD D5 9F 46 7C 45 EA B5 C1 10 F0 41 EF 94 A2 D5 80 67 EB CC 11 05 9E DE 06 A1 9E 5E 71 40 68 4A C8 32 B8 C8 48 73 6D AD 41 51 07 4F 43 E3 C6 7D 8C 7E 49 5D CF A3 D8 3F 29 22 AD 08 AE C4 15 29 90 22 DC 01 5D 81 BA B8 B0 06 ED B1 93 EE CC CC FC 65 97 1F 1F 22 36 AD 85 B1 3D 1B 02 7E C3 0C 1E 6E 4A 30 CB 4F 09 9D 67 C4 D7 DB 06 89 36 A0 7A 03 D0 46 5C 0E C1 B9 24 E4 30 6E BE 0C 60 25 10 57 1E D7 45 CD 0A B3 23 18 1C 47 0C 62 79 29 8F 55 3B F0 0D A2 FB DE 05 B7 71 AD B8 B2 D7 AD 4B 15 E0 ED EB 26 25 CC DE 39 66 8B 1A AE 96 0B E5 4E AB C7 A3 0C 09 82 D6 CD F9 3E 9D 6E C6 73 C5 20 20 F6 8E DF 80 95 13 68 9C 3B C7 EF 71 C9 FC 96 2C 07 48 0A 9A 06 8F 96 7E 90 1F 31 3A 05 86 86 E5 64 5D 5A 08 2C 6C EE 72 7C C2 DF 9B 3C F7 52 5C 17 0E 9B C9 AE 36 8E 54 C9 B5 5E E9 D6 F8 C4 54 81 AC 78 DE 1D 4A C3 31 C6 2E 3F 6D C7 9C FF 5F 7F 88 2C B4 63 CC AC FC 57 1B 84 5D 66 7E C0 14 29 1D 70 74 A8 EE 03 55 07 C2 D7 F5 CE 15 8F 9B DF DA AD C8 F9 33 90 CD 57 98 A1 62 94 E1 3E DD 0F F6 F4 6D 17 35 F1 CE 1D EE 72 72 8B 7A AB B0 7F 68 8F 40 68 96 20 11 BF 05 91 C5 C1 71 AB BF BC A1 9D CC 0B 7D 5F 24 23 3D AB 46 02 43 D6 15 5E 64 BD FD 35 19 C0 47 15 69 C1 EA 0E 9B 5E 8A CD C2 20 CF A7 39 2E 8B D9 89 FC 94 63 18 E9 DD 3F 5B 1B C0 27 4A 85 7A 84 3B C2 50 BC 6A 86 48 0A - // // decrypted by calculated share key: - // 00 00 01 04 - // 00 00 00 0B 01 00 01 50 CE 00 - // 00 00 00 0E 31 39 39 34 37 30 31 30 32 31 D2 D5 37 8A 3C 47 B1 84 E2 94 B2 AF BF 14 70 4D 73 17 BB 38 BE 82 73 DF A2 87 E0 0A 7A BA 8A 81 71 77 1D E1 71 7F B7 C1 66 1D 8C 3D 41 4F 51 09 6A B7 B7 7B 88 28 A6 5A AB 7E 40 25 9B C8 35 9C C6 E2 3A 5F 94 1D 70 0F D7 89 4D 41 6B 7A 29 A2 70 77 3D F8 1D 32 65 D7 D8 D1 6D 13 42 9C 0C 72 DB 48 95 4B 66 EF B9 E6 E4 C1 3B 2C 36 B0 D7 3F E2 85 C8 2A 8C 65 0F 0B 1C F1 A7 C7 E1 1F 0C 32 F5 08 14 AA 5A 43 CD 8E A8 82 14 24 97 63 F0 53 79 4E 33 8D 5F 1C F8 1C 89 3B 39 44 CC A7 63 5F FC BF 87 42 89 2D A5 F4 BC B2 69 49 54 DD AE E6 3F A2 A2 98 DC 3B D4 A2 27 10 F2 06 42 93 C5 30 4A D4 FA F5 BA A5 B2 4B 56 45 59 94 CA 4C 4B 17 55 C7 23 AF F0 8B E5 DC 3A 1B B6 A7 2E 10 BB 9A E7 70 54 BA F5 4B 70 91 - // 03 - - - val data = - """ -E8 6F F6 C1 D9 8B 9C C1 B2 99 A9 53 68 80 E0 4A 34 F9 C2 F7 6D A5 4E E6 25 F1 31 A7 16 46 6D 2A E5 14 2B 64 8D EA 29 15 19 48 69 34 C4 90 D1 50 9A A6 3F 58 69 94 B1 E2 1E E7 C5 D9 53 5B 6B 71 9F 20 9D 2D 02 B3 0D DF B0 F1 0E 03 1E 2C E8 5F F6 28 D6 97 FB A9 45 C6 E1 FF 79 84 C7 7E 42 79 81 BB 48 48 AD D5 9F 46 7C 45 EA B5 C1 10 F0 41 EF 94 A2 D5 80 67 EB CC 11 05 9E DE 06 A1 9E 5E 71 40 68 4A C8 32 B8 C8 48 73 6D AD 41 51 07 4F 43 E3 C6 7D 8C 7E 49 5D CF A3 D8 3F 29 22 AD 08 AE C4 15 29 90 22 DC 01 5D 81 BA B8 B0 06 ED B1 93 EE CC CC FC 65 97 1F 1F 22 36 AD 85 B1 3D 1B 02 7E C3 0C 1E 6E 4A 30 CB 4F 09 9D 67 C4 D7 DB 06 89 36 A0 7A 03 D0 46 5C 0E C1 B9 24 E4 30 6E BE 0C 60 25 10 57 1E D7 45 CD 0A B3 23 18 1C 47 0C 62 79 29 8F 55 3B F0 0D A2 FB DE 05 B7 71 AD B8 B2 D7 AD 4B 15 E0 ED EB 26 25 CC DE 39 66 8B 1A AE 96 0B E5 4E AB C7 A3 0C 09 82 D6 CD F9 3E 9D 6E C6 73 C5 20 20 F6 8E DF 80 95 13 68 9C 3B C7 EF 71 C9 FC 96 2C 07 48 0A 9A 06 8F 96 7E 90 1F 31 3A 05 86 86 E5 64 5D 5A 08 2C 6C EE 72 7C C2 DF 9B 3C F7 52 5C 17 0E 9B C9 AE 36 8E 54 C9 B5 5E E9 D6 F8 C4 54 81 AC 78 DE 1D 4A C3 31 C6 2E 3F 6D C7 9C FF 5F 7F 88 2C B4 63 CC AC FC 57 1B 84 5D 66 7E C0 14 29 1D 70 74 A8 EE 03 55 07 C2 D7 F5 CE 15 8F 9B DF DA AD C8 F9 33 90 CD 57 98 A1 62 94 E1 3E DD 0F F6 F4 6D 17 35 F1 CE 1D EE 72 72 8B 7A AB B0 7F 68 8F 40 68 96 20 11 BF 05 91 C5 C1 71 AB BF BC A1 9D CC 0B 7D 5F 24 23 3D AB 46 02 43 D6 15 5E 64 BD FD 35 19 C0 47 15 69 C1 EA 0E 9B 5E 8A CD C2 20 CF A7 39 2E 8B D9 89 FC 94 63 18 E9 DD 3F 5B 1B C0 27 4A 85 7A 84 3B C2 50 BC 6A 86 48 0A -""".trimIndent().hexToBytes() - try { - println(data.decryptBy("F1 E3 A0 9F AD 63 80 68 43 3C 11 98 53 13 D4 BF".hexToBytes())) - println(data.decryptBy(ByteArray(16)).toUHexString()) - println("With key 16 zero") - } catch (e: Exception) { - println(data.decryptBy("%4;7t>;28;28 = this.readTLVMap() - println("tlvMap: ") - tlvMap.forEach { - println(it.key.toShort().toUHexString() + " = " + it.value.toUHexString()) - } - - tlvMap[0x119]?.let { t119Data -> - t119Data.decryptBy(tgtgtKey).toReadPacket().debugPrintThis("0x119data").apply { - discardExact(2) // always discarded. 00 1C - // 00 1C - // 01 08 00 10 A1 73 76 98 64 E0 38 C6 C8 18 73 FA D3 85 DA D6 01 6A 00 30 1D 99 4A 28 7E B3 B8 AC 74 B9 C4 BB 6D BB 41 72 F7 5C 9F 0F 79 8A 82 4F 1F 69 34 6D 10 D6 BB E8 A3 4A 2B 5D F1 C7 05 3C F8 72 EF CF 67 E4 3C 94 01 06 00 78 B4 ED 9F 44 ED 10 18 A8 85 0A 8A 85 79 45 47 7F 25 AA EE 2C 53 83 80 0A B3 B0 47 3E 95 51 A4 AE 3E CA A0 1D B4 91 F7 BB 2E 94 76 A8 C8 97 02 C4 5B 15 02 B7 03 9A FC C2 58 6D 17 92 46 AE EB 2F 6F 65 B8 69 6C D6 9D AC 18 6F 07 53 AC FE FA BC BD CE 57 13 10 2D 5A C6 50 AA C2 AE 18 D4 FD CD F2 E0 D1 25 29 56 21 35 8F 01 9D D6 69 44 8F 06 D0 23 26 D3 0E E6 E6 B7 01 0C 00 10 73 32 61 4E 2C 72 35 58 68 28 47 3E 2B 6E 52 62 01 0A 00 48 A4 DA 48 FB B4 8D DA 7B 86 D7 A7 FE 01 1B 70 6F 54 F8 55 38 B0 AD 1B 0C 0B B9 F6 94 24 F8 9E 30 32 22 99 0C 22 CD 44 B8 B0 8A A8 65 E1 B8 F0 49 EF E1 23 D7 0D A3 F1 BB 52 B7 4B AF BD 50 EA BF 15 02 78 2B 8B 10 FB 15 01 0D 00 10 29 75 38 72 21 5D 3F 24 37 46 67 79 2B 65 6D 34 01 14 00 60 00 01 5E 19 65 8C 00 58 93 DD 4D 2C 2D 01 44 99 62 B8 7A EF 04 C5 71 0B F1 BE 4C F4 21 F2 97 B0 14 67 0E 14 9F D8 A2 0B 93 40 90 80 F3 59 7A 69 45 D7 D4 53 4C 08 3A 56 1D C9 95 36 2C 7C 5E EE 36 47 5F AE 26 72 76 FD FD 69 E6 0C 2D 3A E8 CF D4 8D 76 C9 17 C3 E3 CD 21 AB 04 6B 70 C5 EC EC 01 0E 00 10 56 48 3E 29 3A 5A 21 74 55 6A 2C 72 58 73 79 71 01 03 00 30 9B A6 5D 85 5C 40 7C 28 E7 05 A9 25 CA F5 FC C0 51 40 85 F3 2F D2 37 F9 09 A6 E6 56 7F 7A 2E 7D 9F B9 1C 00 65 55 D2 A9 60 03 77 AB 6A F5 3F CE 01 33 00 30 F4 3A A7 08 E2 04 FA C8 9D 54 49 DE 63 EA F0 A5 1C C4 03 57 51 B6 AE 0B 55 41 F8 AB 22 F1 DC A3 B0 73 08 55 14 02 BF FF 55 87 42 4C 23 70 91 6A 01 34 00 10 61 C7 02 3F 1D BE A6 27 2F 24 D4 92 95 68 71 EF 05 28 00 1A 7B 22 51 49 4D 5F 69 6E 76 69 74 61 74 69 6F 6E 5F 62 69 74 22 3A 22 31 22 7D 03 22 00 10 CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40 01 1D 00 76 5F 5E 10 E2 34 36 79 27 23 53 4D 65 6B 6A 33 6D 7D 4E 3C 5F 00 60 00 01 5E 19 65 8C 00 58 67 00 9C 02 E4 BC DB A3 93 98 A1 ED 4C 91 08 6F 0C 06 E0 12 6A DC 14 5B 4D 20 7C 82 83 AE 94 53 A2 4A A0 35 FF 59 9D F3 EF 82 42 61 67 2A 31 E7 87 7E 74 E7 A3 E7 5C A8 3C 87 CF 40 6A 9F E5 F7 20 4E 56 C6 4F 1C 98 3A 8B A9 4F 1D 10 35 C2 3B A1 08 7A 89 0B 25 0C 63 01 1F 00 0A 00 01 51 80 00 00 03 84 00 00 01 38 00 0E 00 00 00 01 01 0A 00 27 8D 00 00 00 00 00 01 1A 00 13 02 5B 06 01 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 05 22 00 14 00 00 00 00 76 E4 B8 DD AB 53 02 9F 5E 19 65 8C 20 02 ED BD 05 37 00 17 01 01 00 00 00 00 76 E4 B8 DD 04 AB 53 02 9F 5E 19 65 8C 20 02 ED BD 01 20 00 0A 4D 39 50 57 50 6E 4C 31 65 4F 01 6D 00 2C 31 7A 50 7A 63 72 70 4D 30 43 6E 31 37 4C 32 32 6E 77 2D 36 7A 4E 71 48 48 59 41 35 48 71 77 41 37 6D 76 4F 63 2D 4A 56 77 47 51 5F 05 12 03 5D 00 0E 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 00 2C 6E 4A 72 55 55 74 63 2A 34 7A 32 76 31 66 6A 75 77 6F 6A 65 73 72 76 4F 68 70 66 45 76 4A 75 55 4B 6D 34 43 2D 76 74 38 4D 77 38 5F 00 00 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 00 2C 78 59 35 65 62 4D 74 48 44 6D 30 53 6F 68 56 71 68 33 43 79 79 34 6F 63 65 4A 46 6A 51 58 65 68 30 44 61 75 55 30 6C 78 65 52 6B 5F 00 00 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 00 2C 64 6A 62 79 47 57 45 4F 34 58 34 6A 36 4A 73 48 45 65 6B 73 69 74 72 78 79 62 57 69 77 49 68 46 45 70 72 4A 59 4F 2D 6B 36 47 6F 5F 00 00 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 00 2C 64 4C 31 41 79 32 41 31 74 33 58 36 58 58 2A 74 33 64 4E 70 2A 31 61 2D 50 7A 65 57 67 48 70 2D 65 47 78 6B 59 74 71 62 69 6C 55 5F 00 00 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 2C 75 6A 55 5A 4F 6A 4F 48 52 61 75 6B 32 55 50 38 77 33 34 68 36 69 46 38 2A 77 4E 50 35 2D 66 54 75 37 67 39 56 67 44 57 2A 6B 6F 5F 00 00 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 00 2C 37 47 31 44 6F 54 2D 4D 57 50 63 2D 62 43 46 68 63 62 32 56 38 6E 77 4A 75 41 51 63 54 39 77 45 49 62 57 43 4A 4B 44 4D 6C 6D 34 5F 00 00 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 2C 7A 73 70 5A 56 43 59 45 7A 35 2A 4F 6B 4E 68 6E 74 79 61 69 6E 6F 68 4D 32 6B 41 6C 2A 74 31 63 7A 48 57 77 30 41 6A 4B 50 4B 6B 5F 00 00 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 00 2C 32 6F 2D 51 53 36 65 43 70 37 6A 43 4E 34 6A 74 6E 47 4F 4B 33 67 73 32 63 4A 6F 56 71 58 65 44 48 61 55 39 65 34 2D 32 34 64 30 5F 00 00 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 00 2C 63 54 4D 79 64 51 43 35 50 74 43 45 51 72 6F 33 53 54 41 66 7A 56 2D 44 76 46 56 35 58 6D 56 6B 49 31 68 4C 55 48 4E 65 76 56 38 5F 00 00 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 00 2C 6F 73 72 54 36 32 69 37 66 76 6D 49 50 64 6F 58 4B 48 74 38 58 52 59 56 77 72 7A 6E 69 31 58 7A 57 4C 77 2A 71 36 33 44 74 73 6F 5F 00 00 00 09 74 69 2E 71 71 2E 63 6F 6D 00 2C 41 61 77 4D 78 4D 32 79 58 51 47 75 72 75 55 6C 66 53 58 79 5A 57 48 53 78 52 57 58 50 74 6B 6B 4F 78 6F 66 4A 59 47 6C 71 68 34 5F 00 00 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 00 2C 67 72 57 68 58 77 34 4C 6E 4B 49 4F 67 63 78 45 71 70 33 61 45 67 37 38 46 7A 77 4E 6D 4B 48 56 6E 6F 50 4C 4F 32 6D 57 6D 6E 38 5F 00 00 00 09 71 7A 6F 6E 65 2E 63 6F 6D 00 2C 72 61 47 79 51 35 54 72 4D 55 7A 6E 74 31 4E 52 44 2D 50 72 74 72 41 55 43 35 6A 61 2D 49 47 2D 73 77 4C 6D 49 51 51 41 44 4C 41 5F 00 00 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 00 2C 39 73 2D 4F 51 30 67 76 39 42 6A 37 58 71 52 49 4E 30 35 46 32 64 4D 47 67 47 43 58 57 4A 62 68 63 30 38 63 7A 4B 52 76 6B 78 6B 5F 00 00 03 05 00 10 77 75 6E 54 5F 7E 66 7A 72 40 3C 6E 35 50 53 46 01 43 00 40 3A AE 30 87 81 3D EE BA 31 9C EA 9D 0D D4 73 B1 81 12 E0 94 71 73 7A B0 47 3D 09 47 E5 1B E1 E2 06 1A CB A4 E3 71 9E A6 EA 2A 73 5C C8 D3 B1 2A B1 C7 DA 04 A6 6D 12 26 DF 6B 8B EC C7 12 F8 E1 01 18 00 05 00 00 00 01 00 01 63 00 10 67 6B 60 23 24 6A 55 39 4E 58 24 5E 39 2B 7A 69 01 38 00 5E 00 00 00 09 01 06 00 27 8D 00 00 00 00 00 01 0A 00 24 EA 00 00 00 00 00 01 1C 00 1A 5E 00 00 00 00 00 01 02 00 01 51 80 00 00 00 00 01 03 00 00 1C 20 00 00 00 00 01 20 00 01 51 80 00 00 00 00 01 36 00 1B AF 80 00 00 00 00 01 43 00 1B AF 80 00 00 00 00 01 64 00 1B AF 80 00 00 00 00 01 30 00 0E 00 00 5E 19 65 8C 9F 02 53 AB 00 00 00 00 - val tlvMap119 = this.readTLVMap() - - userStKey = tlvMap119.getOrEmpty(0x10e) - wtSessionTicketKey = tlvMap119.getOrEmpty(0x133) - D2Key = tlvMap119.getOrEmpty(0x305) - DebugLogger.info("userStKey=${userStKey.toUHexString()}") - DebugLogger.info("wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}") - DebugLogger.info("D2Key=${D2Key.toUHexString()}") - } - } - } catch (e: Exception) { - e.printStackTrace() - } - } - } - } - } catch (e: Exception) { - e.printStackTrace() - } - } else // always discarded. 00 1C - // 00 1C - // 01 08 00 10 A1 73 76 98 64 E0 38 C6 C8 18 73 FA D3 85 DA D6 01 6A 00 30 1D 99 4A 28 7E B3 B8 AC 74 B9 C4 BB 6D BB 41 72 F7 5C 9F 0F 79 8A 82 4F 1F 69 34 6D 10 D6 BB E8 A3 4A 2B 5D F1 C7 05 3C F8 72 EF CF 67 E4 3C 94 01 06 00 78 B4 ED 9F 44 ED 10 18 A8 85 0A 8A 85 79 45 47 7F 25 AA EE 2C 53 83 80 0A B3 B0 47 3E 95 51 A4 AE 3E CA A0 1D B4 91 F7 BB 2E 94 76 A8 C8 97 02 C4 5B 15 02 B7 03 9A FC C2 58 6D 17 92 46 AE EB 2F 6F 65 B8 69 6C D6 9D AC 18 6F 07 53 AC FE FA BC BD CE 57 13 10 2D 5A C6 50 AA C2 AE 18 D4 FD CD F2 E0 D1 25 29 56 21 35 8F 01 9D D6 69 44 8F 06 D0 23 26 D3 0E E6 E6 B7 01 0C 00 10 73 32 61 4E 2C 72 35 58 68 28 47 3E 2B 6E 52 62 01 0A 00 48 A4 DA 48 FB B4 8D DA 7B 86 D7 A7 FE 01 1B 70 6F 54 F8 55 38 B0 AD 1B 0C 0B B9 F6 94 24 F8 9E 30 32 22 99 0C 22 CD 44 B8 B0 8A A8 65 E1 B8 F0 49 EF E1 23 D7 0D A3 F1 BB 52 B7 4B AF BD 50 EA BF 15 02 78 2B 8B 10 FB 15 01 0D 00 10 29 75 38 72 21 5D 3F 24 37 46 67 79 2B 65 6D 34 01 14 00 60 00 01 5E 19 65 8C 00 58 93 DD 4D 2C 2D 01 44 99 62 B8 7A EF 04 C5 71 0B F1 BE 4C F4 21 F2 97 B0 14 67 0E 14 9F D8 A2 0B 93 40 90 80 F3 59 7A 69 45 D7 D4 53 4C 08 3A 56 1D C9 95 36 2C 7C 5E EE 36 47 5F AE 26 72 76 FD FD 69 E6 0C 2D 3A E8 CF D4 8D 76 C9 17 C3 E3 CD 21 AB 04 6B 70 C5 EC EC 01 0E 00 10 56 48 3E 29 3A 5A 21 74 55 6A 2C 72 58 73 79 71 01 03 00 30 9B A6 5D 85 5C 40 7C 28 E7 05 A9 25 CA F5 FC C0 51 40 85 F3 2F D2 37 F9 09 A6 E6 56 7F 7A 2E 7D 9F B9 1C 00 65 55 D2 A9 60 03 77 AB 6A F5 3F CE 01 33 00 30 F4 3A A7 08 E2 04 FA C8 9D 54 49 DE 63 EA F0 A5 1C C4 03 57 51 B6 AE 0B 55 41 F8 AB 22 F1 DC A3 B0 73 08 55 14 02 BF FF 55 87 42 4C 23 70 91 6A 01 34 00 10 61 C7 02 3F 1D BE A6 27 2F 24 D4 92 95 68 71 EF 05 28 00 1A 7B 22 51 49 4D 5F 69 6E 76 69 74 61 74 69 6F 6E 5F 62 69 74 22 3A 22 31 22 7D 03 22 00 10 CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40 01 1D 00 76 5F 5E 10 E2 34 36 79 27 23 53 4D 65 6B 6A 33 6D 7D 4E 3C 5F 00 60 00 01 5E 19 65 8C 00 58 67 00 9C 02 E4 BC DB A3 93 98 A1 ED 4C 91 08 6F 0C 06 E0 12 6A DC 14 5B 4D 20 7C 82 83 AE 94 53 A2 4A A0 35 FF 59 9D F3 EF 82 42 61 67 2A 31 E7 87 7E 74 E7 A3 E7 5C A8 3C 87 CF 40 6A 9F E5 F7 20 4E 56 C6 4F 1C 98 3A 8B A9 4F 1D 10 35 C2 3B A1 08 7A 89 0B 25 0C 63 01 1F 00 0A 00 01 51 80 00 00 03 84 00 00 01 38 00 0E 00 00 00 01 01 0A 00 27 8D 00 00 00 00 00 01 1A 00 13 02 5B 06 01 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 05 22 00 14 00 00 00 00 76 E4 B8 DD AB 53 02 9F 5E 19 65 8C 20 02 ED BD 05 37 00 17 01 01 00 00 00 00 76 E4 B8 DD 04 AB 53 02 9F 5E 19 65 8C 20 02 ED BD 01 20 00 0A 4D 39 50 57 50 6E 4C 31 65 4F 01 6D 00 2C 31 7A 50 7A 63 72 70 4D 30 43 6E 31 37 4C 32 32 6E 77 2D 36 7A 4E 71 48 48 59 41 35 48 71 77 41 37 6D 76 4F 63 2D 4A 56 77 47 51 5F 05 12 03 5D 00 0E 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 00 2C 6E 4A 72 55 55 74 63 2A 34 7A 32 76 31 66 6A 75 77 6F 6A 65 73 72 76 4F 68 70 66 45 76 4A 75 55 4B 6D 34 43 2D 76 74 38 4D 77 38 5F 00 00 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 00 2C 78 59 35 65 62 4D 74 48 44 6D 30 53 6F 68 56 71 68 33 43 79 79 34 6F 63 65 4A 46 6A 51 58 65 68 30 44 61 75 55 30 6C 78 65 52 6B 5F 00 00 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 00 2C 64 6A 62 79 47 57 45 4F 34 58 34 6A 36 4A 73 48 45 65 6B 73 69 74 72 78 79 62 57 69 77 49 68 46 45 70 72 4A 59 4F 2D 6B 36 47 6F 5F 00 00 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 00 2C 64 4C 31 41 79 32 41 31 74 33 58 36 58 58 2A 74 33 64 4E 70 2A 31 61 2D 50 7A 65 57 67 48 70 2D 65 47 78 6B 59 74 71 62 69 6C 55 5F 00 00 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 2C 75 6A 55 5A 4F 6A 4F 48 52 61 75 6B 32 55 50 38 77 33 34 68 36 69 46 38 2A 77 4E 50 35 2D 66 54 75 37 67 39 56 67 44 57 2A 6B 6F 5F 00 00 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 00 2C 37 47 31 44 6F 54 2D 4D 57 50 63 2D 62 43 46 68 63 62 32 56 38 6E 77 4A 75 41 51 63 54 39 77 45 49 62 57 43 4A 4B 44 4D 6C 6D 34 5F 00 00 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 2C 7A 73 70 5A 56 43 59 45 7A 35 2A 4F 6B 4E 68 6E 74 79 61 69 6E 6F 68 4D 32 6B 41 6C 2A 74 31 63 7A 48 57 77 30 41 6A 4B 50 4B 6B 5F 00 00 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 00 2C 32 6F 2D 51 53 36 65 43 70 37 6A 43 4E 34 6A 74 6E 47 4F 4B 33 67 73 32 63 4A 6F 56 71 58 65 44 48 61 55 39 65 34 2D 32 34 64 30 5F 00 00 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 00 2C 63 54 4D 79 64 51 43 35 50 74 43 45 51 72 6F 33 53 54 41 66 7A 56 2D 44 76 46 56 35 58 6D 56 6B 49 31 68 4C 55 48 4E 65 76 56 38 5F 00 00 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 00 2C 6F 73 72 54 36 32 69 37 66 76 6D 49 50 64 6F 58 4B 48 74 38 58 52 59 56 77 72 7A 6E 69 31 58 7A 57 4C 77 2A 71 36 33 44 74 73 6F 5F 00 00 00 09 74 69 2E 71 71 2E 63 6F 6D 00 2C 41 61 77 4D 78 4D 32 79 58 51 47 75 72 75 55 6C 66 53 58 79 5A 57 48 53 78 52 57 58 50 74 6B 6B 4F 78 6F 66 4A 59 47 6C 71 68 34 5F 00 00 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 00 2C 67 72 57 68 58 77 34 4C 6E 4B 49 4F 67 63 78 45 71 70 33 61 45 67 37 38 46 7A 77 4E 6D 4B 48 56 6E 6F 50 4C 4F 32 6D 57 6D 6E 38 5F 00 00 00 09 71 7A 6F 6E 65 2E 63 6F 6D 00 2C 72 61 47 79 51 35 54 72 4D 55 7A 6E 74 31 4E 52 44 2D 50 72 74 72 41 55 43 35 6A 61 2D 49 47 2D 73 77 4C 6D 49 51 51 41 44 4C 41 5F 00 00 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 00 2C 39 73 2D 4F 51 30 67 76 39 42 6A 37 58 71 52 49 4E 30 35 46 32 64 4D 47 67 47 43 58 57 4A 62 68 63 30 38 63 7A 4B 52 76 6B 78 6B 5F 00 00 03 05 00 10 77 75 6E 54 5F 7E 66 7A 72 40 3C 6E 35 50 53 46 01 43 00 40 3A AE 30 87 81 3D EE BA 31 9C EA 9D 0D D4 73 B1 81 12 E0 94 71 73 7A B0 47 3D 09 47 E5 1B E1 E2 06 1A CB A4 E3 71 9E A6 EA 2A 73 5C C8 D3 B1 2A B1 C7 DA 04 A6 6D 12 26 DF 6B 8B EC C7 12 F8 E1 01 18 00 05 00 00 00 01 00 01 63 00 10 67 6B 60 23 24 6A 55 39 4E 58 24 5E 39 2B 7A 69 01 38 00 5E 00 00 00 09 01 06 00 27 8D 00 00 00 00 00 01 0A 00 24 EA 00 00 00 00 00 01 1C 00 1A 5E 00 00 00 00 00 01 02 00 01 51 80 00 00 00 00 01 03 00 00 1C 20 00 00 00 00 01 20 00 01 51 80 00 00 00 00 01 36 00 1B AF 80 00 00 00 00 01 43 00 1B AF 80 00 00 00 00 01 64 00 1B AF 80 00 00 00 00 01 30 00 0E 00 00 5E 19 65 8C 9F 02 53 AB 00 00 00 00 - { - PacketLogger.debug("不是oicq response(可能是 UNI/PB)= " + bytes.toUHexString()) - } - } ?: inline { - PacketLogger.error("任何key都无法解密") - return - } - } -} - -private fun Map.getOrEmpty(key: Int): ByteArray { - return this[key] ?: byteArrayOf() -} - -var randomKey: ByteArray = byteArrayOf() -private fun ByteReadPacket.parseOicqResponse(body: ByteReadPacket.() -> Unit) { - readIoBuffer(readInt() - 4).withUse { - check(readByte().toInt() == 2) - this.discardExact(2) // 27 + 2 + body.size - this.discardExact(2) // const, =8001 - this.readUShort() // commandId - this.readShort() // const, =0x0001 - this.readUInt().toLong() // qq - val encryptionMethod = this.readUShort().toInt() - - this.discardExact(1) // const = 0 - @Suppress("UNUSED_VARIABLE") - val packet = when (encryptionMethod) { - 4 -> { // peer public key, ECDH - var data = this.decryptBy(shareKeyCalculatedByConstPubKey, 0, this.readRemaining - 1) - data.read { - println("第一层解密: ${data.toUHexString()}") - val peerShareKey = ECDH.calculateShareKey(loadPrivateKey(ecdhPrivateKeyS), readUShortLVByteArray().adjustToPublicKey()) - body(this.decryptBy(peerShareKey)) - } - } - 0 -> { - val data = if (0 == 0) { - ByteArrayPool.useInstance { byteArrayBuffer -> - val size = this.readRemaining - 1 - this.readFully(byteArrayBuffer, 0, size) - - runCatching { - byteArrayBuffer.decryptBy(shareKeyCalculatedByConstPubKey, size) - }.getOrElse { - byteArrayBuffer.decryptBy(randomKey, size) - } // 这里实际上应该用 privateKey(另一个random出来的key) - } - } else { - this.decryptBy(randomKey, 0, this.readRemaining - 1) - } - - PacketLogger.info("OicqRequest, Real body=" + data.toUHexString()) - body(data.toReadPacket()) - } - else -> error("Illegal encryption method. expected 0 or 4, got $encryptionMethod") - } - } -} - -fun ByteReadPacket.readIoBuffer( - n: Int = remaining.toInt()//not that safe but adequate -): IoBuffer = IoBuffer.Pool.borrow().also { this.readFully(it, n) } - -/** - * 解析 SSO 层包装 - */ -@UseExperimental(ExperimentalUnsignedTypes::class) -private fun parseSsoFrame(flag3: Int, input: ByteReadPacket): KnownPacketFactories.IncomingPacket { - val commandName: String - val ssoSequenceId: Int - - // head - input.readIoBuffer(input.readInt() - 4).withUse { - ssoSequenceId = readInt() - PacketLogger.verbose("sequenceId = $ssoSequenceId") - check(readInt() == 0) - val extraData = readBytes(readInt() - 4) - PacketLogger.verbose("sso(inner)extraData = ${extraData.toUHexString()}") - - commandName = readString(readInt() - 4) - DebugLogger.warning("commandName=$commandName") - readBytes(readInt() - 4) // unknown, sessionId? - //if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}") - - check(readInt() == 0) - } - - // body - val packetFactory = KnownPacketFactories.findPacketFactory(commandName) - - if (packetFactory == null) { - println("找不到包 PacketFactory") - PacketLogger.verbose("传递给 PacketFactory 的数据 = ${input.readBytes().toUHexString()}") - } - - var data = input.readBytes() - if (flag3 == 1) { - data = data.unzip(offset = 4) - } else { - - } - return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, data.toReadPacket(), commandName) -} - - -/** - * 解析 Uni 层包装 - */ -@UseExperimental(ExperimentalUnsignedTypes::class) -private fun parseUniFrame(input: ByteReadPacket): KnownPacketFactories.IncomingPacket { - // 00 00 00 30 00 01 2F 7C 00 00 00 00 00 00 00 04 00 00 00 14 67 78 68 72 65 70 6F 72 74 2E 72 65 70 6F 72 74 00 00 00 08 66 82 D3 0B 00 00 00 00 - // 00 00 00 06 08 00 - - - //00 00 00 2D 00 01 2F 7E 00 00 00 00 00 00 00 04 00 00 00 11 4F 69 64 62 53 76 63 2E 30 78 35 39 66 00 00 00 08 66 82 D3 0B 00 00 00 00 - // 00 00 00 19 08 9F 0B 10 01 18 00 22 0C 10 00 18 00 20 00 A8 01 00 A0 06 01 - - val commandName: String - val ssoSequenceId: Int - - // head - input.readIoBuffer(input.readInt() - 4).withUse { - ssoSequenceId = readInt() - PacketLogger.verbose("sequenceId = $ssoSequenceId") - check(readInt() == 0) - val extraData = readBytes(readInt() - 4) - PacketLogger.verbose("sso(inner)extraData = ${extraData.toUHexString()}") - - commandName = readString(readInt() - 4) - DebugLogger.warning("commandName=$commandName") - readBytes(readInt() - 4) // unknown - //if (unknown.toInt() != 0x02B05B8B) DebugLogger.debug("got new unknown: ${unknown.toUHexString()}") - - check(readInt() == 0) - } - - // body - val packetFactory = KnownPacketFactories.findPacketFactory(commandName) - - if (packetFactory == null) { - println("找不到包 PacketFactory") - PacketLogger.verbose("传递给 PacketFactory 的数据 = ${input.readBytes().toUHexString()}") - } - return KnownPacketFactories.IncomingPacket(packetFactory, ssoSequenceId, input, commandName) -} - -private inline fun inline(block: () -> R): R = block() diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/utils.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/utils.kt deleted file mode 100644 index 749cb91a0..000000000 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/androidPacketTests/utils.kt +++ /dev/null @@ -1,66 +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 - */ - -@file:Suppress("DEPRECATION") - -package androidPacketTests - -import net.mamoe.mirai.utils.cryptor.decryptBy -import org.bouncycastle.jce.provider.JCEECPrivateKey -import org.bouncycastle.jce.spec.ECParameterSpec -import org.bouncycastle.jce.spec.ECPrivateKeySpec -import org.bouncycastle.math.ec.ECConstants -import org.bouncycastle.math.ec.ECCurve -import org.bouncycastle.util.encoders.Hex -import java.math.BigInteger -import java.security.interfaces.ECPrivateKey - -fun ByteArray.decryptBy16Zero() = this.decryptBy(ByteArray(16)) - -fun ByteArray.dropTCPHead(): ByteArray = this.drop(16 * 3 + 6).toByteArray() - - -@Suppress("LocalVariableName") -fun loadPrivateKey(s: String): ECPrivateKey { - fun fromHex( - hex: String - ): BigInteger { - return BigInteger(1, Hex.decode(hex)) - } - - // p = 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1 - // p = 2^192 - 2^32 - 2^12 - 2^8 - 2^7 - 2^6 - 2^3 - 1 - val p = fromHex("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFEE37") - val a = ECConstants.ZERO - val b = BigInteger.valueOf(3) - val n = fromHex("FFFFFFFFFFFFFFFFFFFFFFFE26F2FC170F69466A74DEFD8D") - val h = BigInteger.valueOf(1) - - val curve: ECCurve = ECCurve.Fp(p, a, b) - //ECPoint G = curve.decodePoint(Hex.decode("03" - //+ "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D")); - //ECPoint G = curve.decodePoint(Hex.decode("03" -//+ "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D")); - val G = curve.decodePoint( - Hex.decode( - "04" - + "DB4FF10EC057E9AE26B07D0280B7F4341DA5D1B1EAE06C7D" - + "9B2F2F6D9C5628A7844163D015BE86344082AA88D95E2F9D" - ) - ) - - return JCEECPrivateKey( - "EC", - ECPrivateKeySpec( - fromHex(s), - ECParameterSpec(curve, G, n, h) - ) - ) - // return KeyFactory.getInstance("ECDH").generatePrivate(PKCS8EncodedKeySpec(s)) -} diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt index 14ee4b490..a782d38f3 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/net.mamoe.mirai.qqandroid.io.serialization/JceDecoderTest.kt @@ -17,7 +17,7 @@ import kotlinx.serialization.Serializable import net.mamoe.mirai.qqandroid.io.JceOutput import net.mamoe.mirai.qqandroid.io.JceStruct import net.mamoe.mirai.qqandroid.io.buildJcePacket -import net.mamoe.mirai.utils.cryptor.contentToString +import net.mamoe.mirai.utils.contentToString import net.mamoe.mirai.utils.io.toUHexString import kotlin.test.Test import kotlin.test.assertEquals diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt index d7bc7319d..358ca562a 100644 --- a/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/test/ProtoBufDataClassGenerator.kt @@ -7,8 +7,11 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package test +import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.cryptor.ProtoType import net.mamoe.mirai.utils.cryptor.protoFieldNumber import java.io.File @@ -25,7 +28,7 @@ fun main() { println( File( """ - E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\statsvc\getonline + E:\Projects\QQAndroidFF\app\src\main\java\tencent\im\msgrevoke """.trimIndent() ) .generateUnarrangedClasses().toMutableList().arrangeClasses().joinToString("\n\n") @@ -158,7 +161,7 @@ data class PBFieldInfo( } } -@UseExperimental(ExperimentalUnsignedTypes::class) +@OptIn(ExperimentalUnsignedTypes::class) fun String.generateProtoBufDataClass(): GeneratedClass { if (this.indexOf("extends") == -1) { val javaClassname = substringBetween("class", "{") @@ -364,6 +367,7 @@ fun String.getNumericalValue(): Int? { return this.filter { it in '0'..'9' }.toDoubleOrNull()?.toInt() } +@OptIn(MiraiDebugAPI::class) fun ProtoType.mapToKotlinType(): String { return when (this) { ProtoType.VAR_INT -> "Int" diff --git a/mirai-core-qqandroid/src/jvmTest/kotlin/test/protoBuf.kt b/mirai-core-qqandroid/src/jvmTest/kotlin/test/protoBuf.kt new file mode 100644 index 000000000..7c9793da7 --- /dev/null +++ b/mirai-core-qqandroid/src/jvmTest/kotlin/test/protoBuf.kt @@ -0,0 +1,102 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH") + +package net.mamoe.mirai.utils.cryptor + +import net.mamoe.mirai.utils.MiraiDebugAPI + +// ProtoBuf utilities + + +@Suppress("FunctionName", "SpellCheckingInspection") +/* + * Type Meaning Used For + * 0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum + * 1 64-bit fixed64, sfixed64, double + * 2 Length-delimi string, bytes, embedded messages, packed repeated fields + * 3 Start group Groups (deprecated) + * 4 End group Groups (deprecated) + * 5 32-bit fixed32, sfixed32, float + * + * https://www.jianshu.com/p/f888907adaeb + */ +@MiraiDebugAPI +fun ProtoFieldId(serializedId: UInt): ProtoFieldId = + ProtoFieldId( + protoFieldNumber(serializedId), + protoType(serializedId) + ) + +@MiraiDebugAPI +data class ProtoFieldId( + val fieldNumber: Int, + val type: ProtoType +) { + override fun toString(): String = "$type $fieldNumber" +} + +@Suppress("SpellCheckingInspection") +@MiraiDebugAPI +enum class ProtoType(val value: Byte, private val typeName: String) { + /** + * int32, int64, uint32, uint64, sint32, sint64, bool, enum + */ + VAR_INT(0x00, "varint"), + + /** + * fixed64, sfixed64, double + */ + BIT_64(0x01, " 64bit"), + + /** + * string, bytes, embedded messages, packed repeated fields + */ + LENGTH_DELIMI(0x02, "delimi"), + + /** + * Groups (deprecated) + */ + START_GROUP(0x03, "startg"), + + /** + * Groups (deprecated) + */ + END_GROUP(0x04, " endg"), + + /** + * fixed32, sfixed32, float + */ + BIT_32(0x05, " 32bit"), + ; + + override fun toString(): String = this.typeName + + companion object { + fun valueOf(value: Byte): ProtoType = values().firstOrNull { it.value == value } ?: error("Unknown ProtoType $value") + } +} + +/** + * 由 ProtoBuf 序列化后的 id 得到类型 + * + * serializedId = (fieldNumber << 3) | wireType + */ +@MiraiDebugAPI +fun protoType(number: UInt): ProtoType = + ProtoType.valueOf(number.toInt().shl(29).ushr(29).toByte()) + +/** + * ProtoBuf 序列化后的 id 转为序列前标记的 id + * + * serializedId = (fieldNumber << 3) | wireType + */ +@MiraiDebugAPI +fun protoFieldNumber(number: UInt): Int = number.toInt().ushr(3) diff --git a/mirai-core/build.gradle.kts b/mirai-core/build.gradle.kts index 61dc885ce..fc9b8a63e 100644 --- a/mirai-core/build.gradle.kts +++ b/mirai-core/build.gradle.kts @@ -5,18 +5,16 @@ 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")) - 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 @@ -30,6 +28,9 @@ description = "QQ protocol library" val isAndroidSDKAvailable: Boolean by project +val miraiVersion: String by project +version = miraiVersion + kotlin { if (isAndroidSDKAvailable) { apply(from = rootProject.file("gradle/android.gradle")) @@ -51,49 +52,38 @@ kotlin { ) } - jvm("jvm") { - } + jvm() sourceSets { all { languageSettings.enableLanguageFeature("InlineClasses") - languageSettings.useExperimentalAnnotation("kotlin.Experimental") + } + commonMain { dependencies { api(kotlin("stdlib", kotlinVersion)) api(kotlin("serialization", kotlinVersion)) + api(kotlin("reflect", kotlinVersion)) - api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") + api(kotlinx("coroutines-core-common", coroutinesVersion)) + api(kotlinx("serialization-runtime-common", serializationVersion)) + api(kotlinx("serialization-protobuf-common", serializationVersion)) api(kotlinx("io", kotlinXIoVersion)) api(kotlinx("coroutines-io", coroutinesIoVersion)) api(kotlinx("coroutines-core", coroutinesVersion)) - } - } - commonMain { - dependencies { - api(kotlin("reflect", kotlinVersion)) - api(kotlin("serialization", kotlinVersion)) - api(kotlinx("coroutines-core-common", coroutinesVersion)) - api(kotlinx("serialization-runtime-common", serializationVersion)) - api(ktor("http-cio", ktorVersion)) - api(ktor("http", ktorVersion)) - api(ktor("client-core-jvm", ktorVersion)) + api("org.jetbrains.kotlinx:atomicfu-common:$atomicFuVersion") + api(ktor("client-cio", ktorVersion)) api(ktor("client-core", ktorVersion)) api(ktor("network", ktorVersion)) - //implementation("io.ktor:ktor-io:1.3.0-beta-1") - - //runtimeOnly(files("build/classes/kotlin/metadata/main")) // classpath is not properly set by IDE } } commonTest { dependencies { implementation(kotlin("test-annotations-common")) implementation(kotlin("test-common")) - - //runtimeOnly(files("build/classes/kotlin/metadata/test")) // classpath is not properly set by IDE } } @@ -102,10 +92,11 @@ kotlin { dependencies { api(kotlin("reflect", kotlinVersion)) - api(kotlinx("io", kotlinXIoVersion)) api(kotlinx("io-jvm", kotlinXIoVersion)) api(kotlinx("serialization-runtime", serializationVersion)) + api(kotlinx("serialization-protobuf", serializationVersion)) api(kotlinx("coroutines-android", coroutinesVersion)) + api(kotlinx("coroutines-io-jvm", coroutinesIoVersion)) api(ktor("client-android", ktorVersion)) } @@ -130,9 +121,9 @@ kotlin { api(ktor("client-core-jvm", ktorVersion)) api(kotlinx("io-jvm", kotlinXIoVersion)) api(kotlinx("serialization-runtime", serializationVersion)) - api(kotlinx("coroutines-io", coroutinesIoVersion)) + api(kotlinx("serialization-protobuf", serializationVersion)) api(kotlinx("coroutines-io-jvm", coroutinesIoVersion)) - api(kotlinx("io-jvm", coroutinesIoVersion)) + api(kotlinx("coroutines-core", coroutinesVersion)) api("org.bouncycastle:bcprov-jdk15on:1.64") runtimeOnly(files("build/classes/kotlin/jvm/main")) // classpath is not properly set by IDE @@ -149,4 +140,10 @@ kotlin { } } } -} \ No newline at end of file +} +// +//tasks.withType { +// kotlinOptions.jvmTarget = "1.8" +//} + +apply(from = rootProject.file("gradle/publish.gradle")) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt new file mode 100644 index 000000000..7c832436e --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/Bot.kt @@ -0,0 +1,235 @@ +@file:Suppress("unused") + +package net.mamoe.mirai + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.io.ByteReadChannel +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.data.AddFriendResult +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.network.LoginFailedException +import net.mamoe.mirai.utils.* + +/** + * 机器人对象. 一个机器人实例登录一个 QQ 账号. + * Mirai 为多账号设计, 可同时维护多个机器人. + * + * 注: Bot 为全协程实现, 没有其他任务时若不使用 [join], 主线程将会退出. + * + * @see Contact 联系人 + * @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close]) + */ +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn( + MiraiInternalAPI::class, LowLevelAPI::class, MiraiExperimentalAPI::class, JavaHappyAPI::class +) +actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() { + actual companion object { + /** + * 复制一份此时的 [Bot] 实例列表. + */ + @JvmStatic + actual val instances: List> + get() = BotImpl.instances.toList() + + /** + * 遍历每一个 [Bot] 实例 + */ + actual inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) + + /** + * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] + */ + @JvmStatic + actual fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq) + } + + /** + * [Bot] 运行的 [Context]. + * + * 在 JVM 的默认实现为 `class ContextImpl : Context` + * 在 Android 实现为 [android.content.Context] + */ + actual abstract val context: Context + + /** + * QQ 号码. 实际类型为 uint + */ + actual abstract val uin: Long + + /** + * 昵称 + */ + @MiraiExperimentalAPI("还未支持") + actual val nick: String + get() = ""// TODO("bot 昵称获取") + + /** + * 日志记录器 + */ + actual abstract val logger: MiraiLogger + + // region contacts + + actual abstract val selfQQ: QQ + + /** + * 机器人的好友列表. 它将与服务器同步更新 + */ + @Deprecated( + "use friends instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("this.friends") + ) + actual abstract val qqs: ContactList + + /** + * 机器人的好友列表. 它将与服务器同步更新 + */ + actual abstract val friends: ContactList + + /** + * 获取一个好友或一个群. + */ + @Deprecated( + "use getFriend or getGroup instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException(\"contact id \$id\")") + ) + actual operator fun get(id: Long): Contact { + return this.friends.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException("contact id $id") + } + + /** + * 判断是否有这个 id 的好友或群. + * 在一些情况下这可能会造成歧义. 请考虑后使用. + */ + actual operator fun contains(id: Long): Boolean { + return this.friends.contains(id) || this.groups.contains(id) + } + + /** + * 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException] + */ + actual fun getFriend(id: Long): QQ { + if (id == uin) return selfQQ + return friends.delegate.getOrNull(id) + ?: throw NoSuchElementException("No such friend $id for bot ${this.uin}") + } + + /** + * 机器人加入的群列表. + */ + actual abstract val groups: ContactList + + /** + * 获取一个机器人加入的群. + * + * @throws NoSuchElementException 当不存在这个群时 + */ + actual fun getGroup(id: Long): Group { + return groups.delegate.getOrNull(id) + ?: throw NoSuchElementException("No such group $id for bot ${this.uin}") + } + + // endregion + + // region network + + /** + * 网络模块 + */ + actual abstract val network: BotNetworkHandler + + /** + * 挂起协程直到 [Bot] 下线. + */ + @JvmName("joinSuspend") + @JvmSynthetic + actual suspend inline fun join() = network.join() + + /** + * 登录, 或重新登录. + * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. + * + * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. + * + * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] + * + * @throws LoginFailedException + */ + @JvmName("loginSuspend") + @JvmSynthetic + actual abstract suspend fun login() + // endregion + + + // region actions + + /** + * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * + * @see Bot.recall (扩展函数) 接受参数 [MessageChain] + * @see _lowLevelRecallFriendMessage 低级 API + * @see _lowLevelRecallGroupMessage 低级 API + */ + @JvmName("recallSuspend") + @JvmSynthetic + actual abstract suspend fun recall(source: MessageSource) + + /** + * 获取图片下载链接 + */ + @JvmName("queryImageUrlSuspend") + @JvmSynthetic + actual abstract suspend fun queryImageUrl(image: Image): String + + /** + * 获取图片下载链接并开始下载. + * + * @see ByteReadChannel.copyAndClose + * @see ByteReadChannel.copyTo + */ + @JvmName("openChannelSuspend") + @JvmSynthetic + actual abstract suspend fun openChannel(image: Image): ByteReadChannel + + /** + * 添加一个好友 + * + * @param message 若需要验证请求时的验证消息. + * @param remark 好友备注 + */ + @JvmName("addFriendSuspend") + @JvmSynthetic + @MiraiExperimentalAPI("未支持") + actual abstract suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult + + // endregion + + /** + * 关闭这个 [Bot], 立即取消 [Bot] 的 [kotlinx.coroutines.SupervisorJob]. + * 之后 [kotlinx.coroutines.isActive] 将会返回 `false`. + * + * **注意:** 不可重新登录. 必须重新实例化一个 [Bot]. + * + * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭 + * + * @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭 + */ + actual abstract fun close(cause: Throwable?) + + @OptIn(LowLevelAPI::class, MiraiExperimentalAPI::class) + actual final override fun toString(): String = "Bot(${uin})" +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/BotJavaHappyAPI.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/BotJavaHappyAPI.kt new file mode 100644 index 000000000..7fcf98fbb --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/BotJavaHappyAPI.kt @@ -0,0 +1,223 @@ +package net.mamoe.mirai + +import kotlinx.coroutines.* +import net.mamoe.mirai.contact.PermissionDeniedException +import net.mamoe.mirai.contact.recall +import net.mamoe.mirai.data.AddFriendResult +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.network.LoginFailedException +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +/** + * [Bot] 中为了让 Java 使用者调用更方便的 API 列表. + */ +@MiraiInternalAPI +@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "unused") +actual abstract class BotJavaHappyAPI actual constructor() { + init { + @Suppress("LeakingThis") + assert(this is Bot) + } + + private inline fun runBlocking(crossinline block: suspend Bot.() -> R): R { + return kotlinx.coroutines.runBlocking { block(this@BotJavaHappyAPI as Bot) } + } + + private inline fun future(crossinline block: suspend Bot.() -> R): Future { + return (this as Bot).run { future { block() } } + } + + /** + * 登录, 或重新登录. + * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. + * + * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. + * + * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] + * + * @throws LoginFailedException + */ + @JvmName("login") + fun __loginBlockingForJava__() { + runBlocking { login() } + } + + /** + * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * + * @see Bot.recall (扩展函数) 接受参数 [MessageChain] + */ + @JvmName("recall") + fun __recallBlockingForJava__(source: MessageSource) { + runBlocking { recall(source) } + } + + /** + * 撤回这条消息. + * 根据 [message] 内的 [MessageSource] 进行相关判断. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * @see Bot.recall + */ + @JvmName("recall") + fun __recallBlockingForJava__(message: MessageChain) { + runBlocking { recall(message) } + } + + /** + * 在一段时间后撤回这条消息. + * 将根据 [MessageSource.groupId] 判断消息是群消息还是好友消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @see recall + */ + @JvmName("recallIn") + fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) { + runBlocking { recallIn(source, millis) } + } + + /** + * 在一段时间后撤回这条消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @see recall + */ + @JvmName("recallIn") + fun __recallIn_MemberForJava__(source: MessageChain, millis: Long) { + runBlocking { recallIn(source, millis) } + } + + /** + * 获取图片下载链接 + */ + @JvmName("queryImageUrl") + fun __queryImageUrlBlockingForJava__(image: Image): String { + return runBlocking { queryImageUrl(image) } + } + + /** + * 阻塞当前线程直到 [Bot] 下线. + */ + @JvmName("join") + fun __joinBlockingForJava__() { + runBlocking { join() } + } + + /** + * 添加一个好友 + * + * @param message 若需要验证请求时的验证消息. + * @param remark 好友备注 + */ + @JvmName("addFriend") + fun __addFriendBlockingForJava__( + id: Long, + message: String? = null, + remark: String? = null + ): AddFriendResult { + @OptIn(MiraiExperimentalAPI::class) + return runBlocking { addFriend(id, message, remark) } + } + + /** + * 异步调用 [__loginBlockingForJava__] + */ + @JvmName("loginAsync") + fun __loginAsyncForJava__(): Future { + return future { login() } + } + + /** + * 异步调用 [__recallBlockingForJava__] + */ + @JvmName("recallAsync") + fun __recallAsyncForJava__(source: MessageSource): Future { + return future { recall(source) } + } + + /** + * 异步调用 [__recallBlockingForJava__] + */ + @JvmName("recallAsync") + fun __recallAsyncForJava__(source: MessageChain): Future { + return future { recall(source) } + } + + /** + * 异步调用 [__queryImageUrlBlockingForJava__] + */ + @JvmName("queryImageUrlAsync") + fun __queryImageUrlAsyncForJava__(image: Image): Future { + return future { queryImageUrl(image) } + } +} + +// !! 不要 crossinline, 会编译失败 +@OptIn(ExperimentalCoroutinesApi::class) +internal fun C.future(block: suspend C.() -> R): Future { + val future = object : Future { + val value: CompletableDeferred = CompletableDeferred() + + override fun isDone(): Boolean { + return value.isCompleted + } + + override fun get(): R { + if (value.isCompleted) { + return value.getCompleted() + } + return runBlocking { value.await() } + } + + override fun get(timeout: Long, unit: TimeUnit): R { + if (value.isCompleted) { + return value.getCompleted() + } + return runBlocking { + withTimeoutOrNull(TimeUnit.MILLISECONDS.convert(timeout, unit)) { value.await() } + ?: throw TimeoutException() + } + } + + override fun cancel(mayInterruptIfRunning: Boolean): Boolean { + if (value.isCompleted || value.isCancelled) { + return false + } + + return if (mayInterruptIfRunning && value.isActive) { + value.cancel() + true + } else { + false + } + } + + override fun isCancelled(): Boolean { + return value.isCancelled + } + } + + launch { + @OptIn(ExperimentalCoroutinesApi::class) + future.value.completeWith(kotlin.runCatching { block() }) + } + + return future +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/MiraiEnvironment.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/MiraiEnvironment.kt deleted file mode 100644 index d7fce339d..000000000 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/MiraiEnvironment.kt +++ /dev/null @@ -1,14 +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 - -actual object MiraiEnvironment { - actual val platform: Platform get() = Platform.ANDROID -} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt new file mode 100644 index 000000000..d17f11998 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Contact.kt @@ -0,0 +1,101 @@ +/* + * 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.contact + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +import net.mamoe.mirai.event.events.BeforeImageUploadEvent +import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent +import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent +import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineImage +import net.mamoe.mirai.message.data.id +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException +import net.mamoe.mirai.utils.WeakRefProperty + + +/** + * 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group]. + * + * @author Him188moe + */ +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn(MiraiInternalAPI::class, JavaHappyAPI::class) +actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() { + /** + * 这个联系人所属 [Bot]. + */ + @WeakRefProperty + actual abstract val bot: Bot + /** + * 可以是 QQ 号码或者群号码. + * + * 对于 [QQ], `uin` 与 `id` 是相同的意思. + * 对于 [Group], `groupCode` 与 `id` 是相同的意思. + * + * @see QQ.id + * @see Group.id + */ + actual abstract val id: Long + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. + */ + @JvmName("sendMessageSuspend") + @JvmSynthetic + actual abstract suspend fun sendMessage(message: MessageChain): MessageReceipt + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract suspend fun uploadImage(image: ExternalImage): OfflineImage + + /** + * 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同. + * + * 注: + * [id] 相同的 [Member] 和 [QQ], 他们并不 [equals]. + * 因为, [Member] 含义为群员, 必属于一个群. + * 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人. + */ + actual abstract override fun equals(other: Any?): Boolean + + /** + * @return `bot.hashCode() * 31 + id.hashCode()` + */ + actual abstract override fun hashCode(): Int + + /** + * @return "QQ($id)" or "Group($id)" or "Member($id)" + */ + actual abstract override fun toString(): String +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/ContactJavaHappyAPI.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/ContactJavaHappyAPI.kt new file mode 100644 index 000000000..64b463145 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/ContactJavaHappyAPI.kt @@ -0,0 +1,340 @@ +/* + * 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.contact + +import android.graphics.Bitmap +import kotlinx.coroutines.Dispatchers +import kotlinx.io.core.Input +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +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.future +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.uploadImage +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException +import java.io.File +import java.io.InputStream +import java.net.URL +import java.util.concurrent.Future + +@MiraiInternalAPI +@JavaHappyAPI +@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused") +actual abstract class ContactJavaHappyAPI { + + private inline fun runBlocking(crossinline block: suspend Contact.() -> R): R { + @Suppress("CAST_NEVER_SUCCEEDS") + return kotlinx.coroutines.runBlocking { block(this@ContactJavaHappyAPI as Contact) } + } + + private inline fun future(crossinline block: suspend Contact.() -> R): Future { + @Suppress("CAST_NEVER_SUCCEEDS") + return (this as Contact).run { future { block() } } + } + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. + */ + @Throws(EventCancelledException::class, IllegalStateException::class) + @JvmName("sendMessage") + open fun __sendMessageBlockingForJava__(message: Message): MessageReceipt { + return runBlocking { sendMessage(message) } + } + + @JvmName("sendMessage") + open fun __sendMessageBlockingForJava__(message: String): MessageReceipt { + return runBlocking { sendMessage(message) } + } + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: ExternalImage): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: URL): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: InputStream): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: Input): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: File): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: Bitmap): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 发送消息 + * @see Contact.sendMessage + */ + @JvmName("sendMessageAsync") + open fun __sendMessageAsyncForJava__(message: Message): Future> { + return future { sendMessage(message) } + } + + /** + * 发送消息 + * @see Contact.sendMessage + */ + @JvmName("sendMessageAsync") + open fun __sendMessageAsyncForJava__(message: String): Future> { + return future { sendMessage(message) } + } + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: ExternalImage): Future { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: URL): Future { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: InputStream): Future { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: Input): Future { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: File): Future { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: Bitmap): Future { + return future { uploadImage(image) } + } +} + +@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused", "unused") +@MiraiInternalAPI +@JavaHappyAPI +actual abstract class MemberJavaHappyAPI : QQ() { + private inline fun runBlocking(crossinline block: suspend Member.() -> R): R { + @Suppress("CAST_NEVER_SUCCEEDS") + return kotlinx.coroutines.runBlocking { block(this@MemberJavaHappyAPI as Member) } + } + + private inline fun future(crossinline block: suspend Member.() -> R): Future { + @Suppress("CAST_NEVER_SUCCEEDS") + return (this as Member).run { future { block() } } + } + + + /** + * 禁言. + * + * QQ 中最小操作和显示的时间都是一分钟. + * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @return 机器人无权限时返回 `false` + * + * @see Int.minutesToSeconds + * @see Int.hoursToSeconds + * @see Int.daysToSeconds + * + * @see MemberMuteEvent 成员被禁言事件 + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("mute") + open fun __muteBlockingForJava__(seconds: Int) { + return runBlocking { mute(seconds) } + } + + /** + * 解除禁言. + * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * + * @see MemberUnmuteEvent 成员被取消禁言事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("unmute") + open fun __unmuteBlockingForJava__() { + return runBlocking { unmute() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kick") + open fun __kickBlockingForJava__(message: String) { + return runBlocking { kick() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kick") + open fun __kickBlockingForJava__() = __kickBlockingForJava__("") + + + /** + * 禁言. + * + * QQ 中最小操作和显示的时间都是一分钟. + * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @return 机器人无权限时返回 `false` + * + * @see Int.minutesToSeconds + * @see Int.hoursToSeconds + * @see Int.daysToSeconds + * + * @see MemberMuteEvent 成员被禁言事件 + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("muteAsync") + open fun __muteAsyncForJava__(seconds: Int): Future { + return future { mute(seconds) } + } + + /** + * 解除禁言. + * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * + * @see MemberUnmuteEvent 成员被取消禁言事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("unmuteAsync") + open fun __unmuteAsyncForJava__(): Future { + return future { unmute() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kickAsync") + open fun __kickAsyncForJava__(message: String): Future { + return future { kick() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kickAsync") + open fun __kickAsyncForJava__(): Future = __kickAsyncForJava__("") +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt new file mode 100644 index 000000000..c1a6d1c21 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Group.kt @@ -0,0 +1,239 @@ +/* + * 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.contact + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot +import net.mamoe.mirai.data.MemberInfo +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.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineGroupImage +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException + +/** + * 群. 在 QQ Android 中叫做 "Troop" + */ +@Suppress("INAPPLICABLE_JVM_NAME") +actual abstract class Group : Contact(), CoroutineScope { + /** + * 群名称. + * + * 在修改时将会异步上传至服务器. + * 频繁修改可能会被服务器拒绝. + * + * @see MemberPermissionChangeEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var name: String + + /** + * 入群公告, 没有时为空字符串. + * + * 在修改时将会异步上传至服务器. + * + * @see GroupEntranceAnnouncementChangeEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var entranceAnnouncement: String + + /** + * 全体禁言状态. `true` 为开启. + * + * 当前仅能修改状态. + * + * @see GroupMuteAllEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var isMuteAll: Boolean + + /** + * 坦白说状态. `true` 为允许. + * + * 在修改时将会异步上传至服务器. + * + * @see GroupAllowConfessTalkEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var isConfessTalkEnabled: Boolean + + /** + * 允许群员邀请好友入群的状态. `true` 为允许 + * + * 在修改时将会异步上传至服务器. + * + * @see GroupAllowMemberInviteEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var isAllowMemberInvite: Boolean + + /** + * 自动加群审批 + */ + actual abstract val isAutoApproveEnabled: Boolean + + /** + * 匿名聊天 + */ + actual abstract val isAnonymousChatEnabled: Boolean + + /** + * 同为 groupCode, 用户看到的群号码. + */ + actual abstract override val id: Long + + /** + * 群主. + * + * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 + */ + actual abstract val owner: Member + + /** + * [Bot] 在群内的 [Member] 实例 + */ + @MiraiExperimentalAPI + actual abstract val botAsMember: Member + + /** + * 机器人被禁言还剩余多少秒 + * + * @see BotMuteEvent 机器人被禁言事件 + * @see isBotMuted 判断机器人是否正在被禁言 + */ + actual abstract val botMuteRemaining: Int + + /** + * 机器人在这个群里的权限 + * + * @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限 + * @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator] + * + * @see BotGroupPermissionChangeEvent 机器人群员修改 + */ + actual abstract val botPermission: MemberPermission + + /** + * 群头像下载链接. + */ + actual val avatarUrl: String + get() = "https://p.qlogo.cn/gh/$id/${id}_1/640" + + /** + * 群成员列表, 不含机器人自己, 含群主. + * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新 + */ + actual abstract val members: ContactList + + /** + * 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException] + */ + actual abstract operator fun get(id: Long): Member + + /** + * 获取群成员实例, 不存在则 null + */ + actual abstract fun getOrNull(id: Long): Member? + + /** + * 检查此 id 的群成员是否存在 + */ + actual abstract operator fun contains(id: Long): Boolean + + /** + * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun quit(): Boolean + + /** + * 构造一个 [Member]. + * 非特殊情况请不要使用这个函数. 优先使用 [get]. + */ + @JvmName("newMember") + @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName") + @MiraiExperimentalAPI("dangerous") + actual abstract fun Member(memberInfo: MemberInfo): Member + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmName("sendMessageSuspend") + @JvmSynthetic + actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage + + actual companion object { + /** + * by @kar98k + */ + actual fun calculateGroupUinByGroupCode(groupCode: Long): Long { + var left: Long = groupCode / 1000000L + + when (left) { + in 0..10 -> left += 202 + in 11..19 -> left += 480 - 11 + in 20..66 -> left += 2100 - 20 + in 67..156 -> left += 2010 - 67 + in 157..209 -> left += 2147 - 157 + in 210..309 -> left += 4100 - 210 + in 310..499 -> left += 3800 - 310 + } + + return left * 1000000L + groupCode % 1000000L + } + + actual fun calculateGroupCodeByGroupUin(groupUin: Long): Long { + var left: Long = groupUin / 1000000L + + when (left) { + in 0 + 202..10 + 202 -> left -= 202 + in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11 + in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20 + in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67 + in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157 + in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210 + in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310 + } + + return left * 1000000L + groupUin % 1000000L + } + } + + @MiraiExperimentalAPI + actual fun toFullString(): String { + return "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" + } + +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Member.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Member.kt new file mode 100644 index 000000000..bc017e019 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/Member.kt @@ -0,0 +1,126 @@ +/* + * 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("unused") + +package net.mamoe.mirai.contact + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.WeakRefProperty + +/** + * 群成员. + */ +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn(MiraiInternalAPI::class, JavaHappyAPI::class) +actual abstract class Member : MemberJavaHappyAPI() { + /** + * 所在的群. + */ + @WeakRefProperty + actual abstract val group: Group + /** + * 成员的权限, 动态更新. + * + * @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发. + */ + actual abstract val permission: MemberPermission + /** + * 群名片. 可能为空. + * + * 管理员和群主都可修改任何人(包括群主)的群名片. + * + * 在修改时将会异步上传至服务器. + * + * @see [nameCardOrNick] 获取非空群名片或昵称 + * + * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. + * @throws PermissionDeniedException 无权限修改时 + */ + actual abstract var nameCard: String + /** + * 群头衔. + * + * 仅群主可以修改群头衔. + * + * 在修改时将会异步上传至服务器. + * + * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. + * @throws PermissionDeniedException 无权限修改时 + */ + actual abstract var specialTitle: String + /** + * 被禁言剩余时长. 单位为秒. + * + * @see isMuted 判断改成员是否处于禁言状态 + * @see mute 设置禁言 + * @see unmute 取消禁言 + */ + actual abstract val muteTimeRemaining: Int + + /** + * 禁言. + * + * QQ 中最小操作和显示的时间都是一分钟. + * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @return 机器人无权限时返回 `false` + * + * @see Int.minutesToSeconds + * @see Int.hoursToSeconds + * @see Int.daysToSeconds + * + * @see MemberMuteEvent 成员被禁言事件 + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("muteSuspend") + @JvmSynthetic + actual abstract suspend fun mute(durationSeconds: Int) + + /** + * 解除禁言. + * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * + * @see MemberUnmuteEvent 成员被取消禁言事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("unmuteSuspend") + @JvmSynthetic + actual abstract suspend fun unmute() + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kickSuspend") + @JvmSynthetic + actual abstract suspend fun kick(message: String) + + /** + * 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true + */ + actual abstract override fun equals(other: Any?): Boolean + + /** + * @return `bot.hashCode() * 31 + id.hashCode()` + */ + actual abstract override fun hashCode(): Int + +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt new file mode 100644 index 000000000..a33e7931a --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/contact/QQ.kt @@ -0,0 +1,105 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.contact + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot +import net.mamoe.mirai.data.FriendNameRemark +import net.mamoe.mirai.data.PreviousNameList +import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.event.events.BeforeImageUploadEvent +import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent +import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent +import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException + +/** + * QQ 对象. + * 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot]. + * 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取. + * + * 对于同一个 [Bot] 任何一个人的 [QQ] 实例都是单一的. + * + * A QQ instance helps you to receive event from or sendPacket event to. + * Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same. + * + * @author Him188moe + */ +@Suppress("INAPPLICABLE_JVM_NAME") +actual abstract class QQ : Contact(), CoroutineScope { + /** + * 请求头像下载链接 + */ + // @MiraiExperimentalAPI + //suspend fun queryAvatar(): AvatarLink + /** + * QQ 号码 + */ + actual abstract override val id: Long + /** + * 昵称 + */ + actual abstract val nick: String + + /** + * 查询用户资料 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun queryProfile(): Profile + + /** + * 头像下载链接 + */ + actual val avatarUrl: String + get() = "http://q1.qlogo.cn/g?b=qq&nk=$id&s=640" + + /** + * 查询曾用名. + * + * 曾用名可能是: + * - 昵称 + * - 共同群内的群名片 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun queryPreviousNameList(): PreviousNameList + + /** + * 查询机器人账号给这个人设置的备注 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun queryRemark(): FriendNameRemark + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmName("sendMessageSuspend") + @JvmSynthetic + actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/event/internal/MiraiAtomicBoolean.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/event/internal/MiraiAtomicBoolean.kt new file mode 100644 index 000000000..1b3fb785f --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/event/internal/MiraiAtomicBoolean.kt @@ -0,0 +1,17 @@ +package net.mamoe.mirai.event.internal + +import java.util.concurrent.atomic.AtomicBoolean + +internal actual class MiraiAtomicBoolean actual constructor(initial: Boolean) { + private val delegate: AtomicBoolean = AtomicBoolean(initial) + + actual fun compareAndSet(expect: Boolean, update: Boolean): Boolean { + return delegate.compareAndSet(expect, update) + } + + actual var value: Boolean + get() = delegate.get() + set(value) { + delegate.set(value) + } +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt index 25d9e7640..b20f87bd8 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.message -import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.utils.MiraiInternalAPI @@ -17,8 +16,8 @@ import net.mamoe.mirai.utils.MiraiInternalAPI /** * 平台相关扩展 */ -@UseExperimental(MiraiInternalAPI::class) -actual abstract class MessagePacket actual constructor(bot: Bot) : MessagePacketBase(bot) { +@OptIn(MiraiInternalAPI::class) +actual abstract class MessagePacket actual constructor() : MessagePacketBase() { // suspend inline fun uploadImage(image: Bitmap): Image = subject.uploadImage(image) //suspend inline fun uploadImage(image: URL): Image = subject.uploadImage(image) //suspend inline fun uploadImage(image: Input): Image = subject.uploadImage(image) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt new file mode 100644 index 000000000..49755320a --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt @@ -0,0 +1,142 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.message + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +import net.mamoe.mirai.LowLevelAPI +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.recallIn +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.getValue +import net.mamoe.mirai.utils.unsafeWeakRef + +/** + * 发送消息后得到的回执. 可用于撤回. + * + * 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问. + * + * @see Group.sendMessage 发送群消息, 返回回执(此对象) + * @see QQ.sendMessage 发送群消息, 返回回执(此对象) + * + * @see MessageReceipt.sourceId 源 id + * @see MessageReceipt.sourceSequenceId 源序列号 + * @see MessageReceipt.sourceTime 源时间 + */ +@Suppress("FunctionName") +@OptIn(MiraiInternalAPI::class) +actual open class MessageReceipt actual constructor( + actual val source: MessageSource, + target: C, + private val botAsMember: Member? +) { + init { + require(target is Group || target is QQ) { "target must be either Group or QQ" } + } + + /** + * 发送目标, 为 [Group] 或 [QQ] + */ + actual val target: C by target.unsafeWeakRef() + + /** + * 是否为发送给群的消息的回执 + */ + actual val isToGroup: Boolean = botAsMember != null + + private val _isRecalled = atomic(false) + + /** + * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. + * + * @see Bot.recall + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + actual suspend fun recall() { + @Suppress("BooleanLiteralArgument") + if (_isRecalled.compareAndSet(false, true)) { + when (val contact = target) { + is Group -> { + contact.bot.recall(source) + } + is QQ -> { + TODO() + } + else -> error("Unknown contact type") + } + } else error("message is already or planned to be recalled") + } + + /** + * 在一段时间后撤回这条消息.. [recall] 或 [recallIn] 只能被调用一次. + * + * @param millis 延迟时间, 单位为毫秒 + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + actual fun recallIn(millis: Long): Job { + @Suppress("BooleanLiteralArgument") + if (_isRecalled.compareAndSet(false, true)) { + return when (val contact = target) { + is QQ, + is Group -> contact.bot.recallIn(source, millis) + else -> error("Unknown contact type") + } + } else error("message is already or planned to be recalled") + } + + /** + * [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息. + * @see MessageChain.quote 引用一条消息 + */ + actual open suspend fun quote(): QuoteReplyToSend { + this.source.ensureSequenceIdAvailable() + @OptIn(LowLevelAPI::class) + return _unsafeQuote() + } + + /** + * 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable]. + * 在 sequenceId 可用前就发送这条消息则会导致一个异常. + * 当且仅当用于存储而不用于发送时使用这个方法. + * + * @see MessageChain.quote 引用一条消息 + */ + @LowLevelAPI + @Suppress("FunctionName") + actual fun _unsafeQuote(): QuoteReplyToSend { + return this.source.quote(botAsMember as? QQ) + } + + /** + * 引用这条消息并回复. + * @see MessageChain.quote 引用一条消息 + */ + @JvmName("quoteReplySuspend") + @JvmSynthetic + actual suspend fun quoteReply(message: MessageChain) { + target.sendMessage(this.quote() + message) + } + + + @JavaHappyAPI + @JvmName("quoteReply") + fun __quoteReplyBlockingForJava__(message: Message) { + runBlocking { quoteReply(message) } + } + + @JavaHappyAPI + @JvmName("recall") + fun __recallBlockingForJava__() { + runBlocking { recall() } + } + + @JavaHappyAPI + @JvmName("quote") + fun __quoteBlockingForJava__() { + runBlocking { quote() } + } +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/SendImageUtilsAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/SendImageUtilsAndroid.kt new file mode 100644 index 000000000..d38892940 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/message/SendImageUtilsAndroid.kt @@ -0,0 +1,200 @@ +/* + * 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("unused") + +package net.mamoe.mirai.message + +import android.graphics.Bitmap +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import kotlinx.io.core.Input +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.utils.OverFileSizeMaxException +import net.mamoe.mirai.utils.sendTo +import net.mamoe.mirai.utils.toExternalImage +import net.mamoe.mirai.utils.upload +import java.io.File +import java.io.InputStream +import java.net.URL + +/* + * 发送图片的一些扩展函数. + */ + +// region IMAGE.sendAsImageTo(Contact) + +/** + * 在 [Dispatchers.IO] 中将图片发送到指定联系人. 不会创建临时文件 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun Bitmap.sendTo(contact: C): MessageReceipt = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + +/** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun URL.sendAsImageTo(contact: C): MessageReceipt = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + +/** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun Input.sendAsImageTo(contact: C): MessageReceipt = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + +/** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun InputStream.sendAsImageTo(contact: C): MessageReceipt = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + +/** + * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun File.sendAsImageTo(contact: C): MessageReceipt { + require(this.exists() && this.canRead()) + return withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) +} + +// endregion + +// region IMAGE.Upload(Contact): Image + +/** + * 在 [Dispatchers.IO] 中将图片上传后构造 [Image]. 不会创建临时文件 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun Bitmap.upload(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + +/** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传后构造 [Image] + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun URL.uploadAsImage(contact: Contact): Image = + withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + +/** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传后构造 [Image] + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun Input.uploadAsImage(contact: Contact): Image = + withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + +/** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image] + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun InputStream.uploadAsImage(contact: Contact): Image = + withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) + +/** + * 在 [Dispatchers.IO] 中将文件作为图片上传后构造 [Image] + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend fun File.uploadAsImage(contact: Contact): Image { + require(this.exists() && this.canRead()) + return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) +} + +// endregion + +// region Contact.sendImage(IMAGE) + +/** + * 在 [Dispatchers.IO] 中将图片发送到指定联系人. 不会保存临时文件 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun C.sendImage(bufferedImage: Bitmap): MessageReceipt = bufferedImage.sendTo(this) + +/** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun C.sendImage(imageUrl: URL): MessageReceipt = imageUrl.sendAsImageTo(this) + +/** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun C.sendImage(imageInput: Input): MessageReceipt = imageInput.sendAsImageTo(this) + +/** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun C.sendImage(imageStream: InputStream): MessageReceipt = + imageStream.sendAsImageTo(this) + +/** + * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun C.sendImage(file: File): MessageReceipt = file.sendAsImageTo(this) + +// endregion + +// region Contact.uploadImage(IMAGE) + +/** + * 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun Contact.uploadImage(bufferedImage: Bitmap): Image = bufferedImage.upload(this) + +/** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this) + +/** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this) + +/** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this) + +/** + * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ +@Throws(OverFileSizeMaxException::class) +suspend inline fun Contact.uploadImage(file: File): Image = file.uploadAsImage(this) + +// endregion \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt index e781c9697..75e32ef35 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/BotConfigurationAndroid.kt @@ -9,29 +9,11 @@ package net.mamoe.mirai.utils -import kotlinx.io.core.IoBuffer import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -/** - * 在各平台实现的默认的验证码处理器. - */ -actual var defaultLoginSolver: LoginSolver = object : LoginSolver() { - override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? { - error("should be implemented manually by you") - } - - override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { - error("should be implemented manually by you") - } - - override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { - error("should be implemented manually by you") - } -} - @Suppress("ClassName", "PropertyName") actual open class BotConfiguration actual constructor() { /** @@ -68,15 +50,15 @@ actual open class BotConfiguration actual constructor() { /** * 重连失败后, 继续尝试的每次等待时间 */ - actual var reconnectPeriodMillis: Long = 60.secondsToMillis + actual var reconnectPeriodMillis: Long = 5.secondsToMillis /** * 最多尝试多少次重连 */ - actual var reconnectionRetryTimes: Int = 3 + actual var reconnectionRetryTimes: Int = Int.MAX_VALUE /** * 验证码处理器 */ - actual var loginSolver: LoginSolver = defaultLoginSolver + actual var loginSolver: LoginSolver = LoginSolver.Default actual companion object { /** @@ -115,4 +97,31 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: */ @BotConfigurationDsl companion object ByDeviceDotJson +} + +/** + * 验证码, 设备锁解决器 + */ +actual abstract class LoginSolver { + actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? + actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? + actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? + + actual companion object { + actual val Default: LoginSolver + get() = object : LoginSolver() { + override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? { + error("should be implemented manually by you") + } + + override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { + error("should be implemented manually by you") + } + + override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { + error("should be implemented manually by you") + } + } + } + } \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImageAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImageAndroid.kt index a880b7fcd..2f433f224 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImageAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/ExternalImageAndroid.kt @@ -7,10 +7,11 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.utils +import android.graphics.Bitmap import android.graphics.BitmapFactory import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.withContext @@ -28,9 +29,22 @@ import java.net.URL * 将各类型图片容器转为 [ExternalImage] */ + +/** + * 读取 [Bitmap] 的属性, 然后构造 [ExternalImage] + */ +@Suppress("UNUSED_PARAMETER") +@Throws(IOException::class) +fun Bitmap.toExternalImage(formatName: String = "gif"): ExternalImage { + TODO() +} + +// suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } + /** * 读取文件头识别图片属性, 然后构造 [ExternalImage] */ +@OptIn(MiraiInternalAPI::class) @Throws(IOException::class) fun File.toExternalImage(): ExternalImage { val input = BitmapFactory.decodeFile(this.absolutePath) @@ -39,7 +53,7 @@ fun File.toExternalImage(): ExternalImage { return ExternalImage( width = input.width, height = input.height, - md5 = this.inputStream().use { it.md5() }, + md5 = this.inputStream().use { MiraiPlatformUtils.md5(it) }, imageFormat = this.nameWithoutExtension, input = this.inputStream().asInput(IoBuffer.Pool), inputSize = this.length(), @@ -50,8 +64,7 @@ fun File.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [File.toExternalImage] */ -@Suppress("unused") -suspend fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** * 下载文件到临时目录然后调用 [File.toExternalImage] @@ -63,6 +76,7 @@ fun URL.toExternalImage(): ExternalImage { openStream().asInput().use { input -> input.copyTo(output) } + output.flush() } return file.toExternalImage() } @@ -70,8 +84,7 @@ fun URL.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [URL.toExternalImage] */ -@Suppress("unused") -suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** * 保存为临时文件然后调用 [File.toExternalImage] @@ -79,8 +92,9 @@ suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toEx @Throws(IOException::class) fun InputStream.toExternalImage(): ExternalImage { val file = createTempFile().apply { deleteOnExit() } - file.outputStream().asOutput().use { - this.asInput().copyTo(it) + file.outputStream().use { + this.copyTo(it) + it.flush() } this.close() return file.toExternalImage() @@ -89,17 +103,19 @@ fun InputStream.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [InputStream.toExternalImage] */ -@Suppress("unused") -suspend fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } /** - * 保存为临时文件然后调用 [File.toExternalImage] + * 保存为临时文件然后调用 [File.toExternalImage]. + * + * 需要函数调用者 close [this] */ @Throws(IOException::class) fun Input.toExternalImage(): ExternalImage { val file = createTempFile().apply { deleteOnExit() } file.outputStream().asOutput().use { this.copyTo(it) + it.flush() } return file.toExternalImage() } @@ -107,5 +123,18 @@ fun Input.toExternalImage(): ExternalImage { /** * 在 [IO] 中进行 [Input.toExternalImage] */ -@Suppress("unused") -suspend fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } +suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } + +/* +/** + * 保存为临时文件然后调用 [File.toExternalImage]. + */ +suspend fun ByteReadChannel.toExternalImage(): ExternalImage { + val file = createTempFile().apply { deleteOnExit() } + file.outputStream().use { + withContext(IO) { copyTo(it) } + it.flush() + } + + return file.suspendToExternalImage() +}*/ \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/OverFileSizeMaxException.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/OverFileSizeMaxException.kt new file mode 100644 index 000000000..4a31ad010 --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/OverFileSizeMaxException.kt @@ -0,0 +1,6 @@ +package net.mamoe.mirai.utils + +/** + * 图片文件过大 + */ +actual class OverFileSizeMaxException : IllegalStateException() \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt deleted file mode 100644 index 2ba68a6ba..000000000 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsAndroid.kt +++ /dev/null @@ -1,100 +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.utils - -import io.ktor.client.HttpClient -import io.ktor.client.engine.cio.CIO -import io.ktor.util.KtorExperimentalAPI -import kotlinx.io.pool.useInstance -import net.mamoe.mirai.utils.io.ByteArrayPool -import java.io.ByteArrayOutputStream -import java.io.DataInput -import java.io.EOFException -import java.io.InputStream -import java.net.InetAddress -import java.security.MessageDigest -import java.util.zip.Inflater - - -/** - * Ktor HttpClient. 不同平台使用不同引擎. - */ -@UseExperimental(KtorExperimentalAPI::class) -actual val Http: HttpClient - get() = HttpClient(CIO) - -/** - * Localhost 解析 - */ -actual fun localIpAddress(): String = runCatching { - InetAddress.getLocalHost().hostAddress -}.getOrElse { "192.168.1.123" } - -/** - * MD5 算法 - * - * @return 16 bytes - */ -actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray) - -fun InputStream.md5(): ByteArray { - val digest = MessageDigest.getInstance("md5") - digest.reset() - this.readInSequence { - digest.update(it.toByte()) - } - return digest.digest() -} - -fun DataInput.md5(): ByteArray { - val digest = MessageDigest.getInstance("md5") - digest.reset() - val buffer = byteArrayOf(1) - while (true) { - try { - this.readFully(buffer) - } catch (e: EOFException) { - break - } - digest.update(buffer[0]) - } - return digest.digest() -} - -private inline fun InputStream.readInSequence(block: (Int) -> Unit) { - var read: Int - while (this.read().also { read = it } != -1) { - block(read) - } -} - -actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { - this.checkOffsetAndLength(offset, length) - if (length == 0) return ByteArray(0) - - val inflater = Inflater() - inflater.reset() - ByteArrayOutputStream().use { output -> - inflater.setInput(this, offset, length) - ByteArrayPool.useInstance { - while (!inflater.finished()) { - output.write(it, 0, inflater.inflate(it)) - } - } - - inflater.end() - return output.toByteArray() - } -} - -/** - * 时间戳 - */ -actual val currentTimeMillis: Long get() = System.currentTimeMillis() \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt index 11990fa36..3466ec9b6 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.utils import android.annotation.SuppressLint @@ -18,23 +20,29 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration +import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress +import net.mamoe.mirai.utils.MiraiPlatformUtils.md5 import java.io.File /** * 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存. */ -@UseExperimental(UnstableDefault::class) +@OptIn(UnstableDefault::class) fun File.loadAsDeviceInfo(context: Context): DeviceInfo { if (!this.exists() || this.length() == 0L) { return SystemDeviceInfo(context).also { - this.writeText(Json.plain.stringify(SystemDeviceInfo.serializer(), it)) + this.writeText(JSON.stringify(SystemDeviceInfo.serializer(), it)) } } - return Json.nonstrict.parse(DeviceInfoData.serializer(), this.readText()).also { + return JSON.parse(DeviceInfoData.serializer(), this.readText()).also { it.context = context } } +@OptIn(UnstableDefault::class) +private val JSON = Json(JsonConfiguration.Default) + /** * 部分引用指向 [Build]. * 部分需要权限, 若无权限则会使用默认值. @@ -96,6 +104,7 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { (context.applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).connectionInfo.ssid.toByteArray() }.getOrElse { byteArrayOf() } + @OptIn(MiraiInternalAPI::class) override val imsiMd5: ByteArray @SuppressLint("HardwareIds") get() = md5(kotlin.runCatching { @@ -111,6 +120,8 @@ actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { (context.applicationContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager).deviceId } }.getOrElse { "" } + + @OptIn(MiraiInternalAPI::class) override val ipAddress: ByteArray get() = localIpAddress().split(".").map { it.toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf() override val androidId: ByteArray get() = Build.ID.toByteArray() override val apn: ByteArray get() = "wifi".toByteArray() diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt new file mode 100644 index 000000000..8bf594b5a --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt @@ -0,0 +1,24 @@ +package net.mamoe.mirai.utils + +import android.os.Build + +private var isAddSuppressedSupported: Boolean = true + +@PublishedApi +internal actual fun Throwable.addSuppressedMirai(e: Throwable) { + if (this == e) { + return + } + if (!isAddSuppressedSupported) { + return + } + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + this.addSuppressed(e) + } else { + isAddSuppressedSupported = false + } + } catch (e: Exception) { + isAddSuppressedSupported = false + } +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt index 98db0234f..c5b1df78a 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHAndroid.kt @@ -9,8 +9,11 @@ package net.mamoe.mirai.utils.cryptor -import net.mamoe.mirai.utils.md5 +import android.annotation.SuppressLint +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiPlatformUtils.md5 import java.security.* +import java.security.spec.ECGenParameterSpec import java.security.spec.X509EncodedKeySpec import javax.crypto.KeyAgreement @@ -18,13 +21,13 @@ import javax.crypto.KeyAgreement actual typealias ECDHPrivateKey = PrivateKey actual typealias ECDHPublicKey = PublicKey -actual class ECDHKeyPair( +internal actual class ECDHKeyPairImpl( private val delegate: KeyPair -) { - actual val privateKey: ECDHPrivateKey get() = delegate.private - actual val publicKey: ECDHPublicKey get() = delegate.public +) : ECDHKeyPair { + override val privateKey: ECDHPrivateKey get() = delegate.private + override val publicKey: ECDHPublicKey get() = delegate.public - actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) + override val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) } @Suppress("FunctionName") @@ -32,10 +35,44 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair()) actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual companion object { - actual fun generateKeyPair(): ECDHKeyPair { - return ECDHKeyPair(KeyPairGenerator.getInstance("ECDH").genKeyPair()) + @Suppress("ObjectPropertyName") + private var _isECDHAvailable: Boolean = false // because `runCatching` has no contract. + actual val isECDHAvailable: Boolean get() = _isECDHAvailable + + init { + kotlin.runCatching { + @SuppressLint("PrivateApi") + val clazz = Class.forName( + "com.android.org.bouncycastle.jce.provider.BouncyCastleProvider", + true, + ClassLoader.getSystemClassLoader() + ) + + val providerName = clazz.getDeclaredField("PROVIDER_NAME").get(null) as String + + if (Security.getProvider(providerName) != null) { + Security.removeProvider(providerName) + } + Security.addProvider(clazz.newInstance() as Provider) + generateKeyPair() + _isECDHAvailable = true + }.exceptionOrNull()?.let { + throw IllegalStateException("cannot init BouncyCastle", it) + } + _isECDHAvailable = false } + + actual fun generateKeyPair(): ECDHKeyPair { + if (!isECDHAvailable) { + return ECDHKeyPair.DefaultStub + } + return ECDHKeyPairImpl(KeyPairGenerator.getInstance("ECDH") + .also { it.initialize(ECGenParameterSpec("secp192k1")) } + .genKeyPair()) + } + + @OptIn(MiraiInternalAPI::class) actual fun calculateShareKey( privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt deleted file mode 100644 index aa7da3584..000000000 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt +++ /dev/null @@ -1,46 +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.utils.cryptor - -import net.mamoe.mirai.utils.MiraiDebugAPI -import java.lang.reflect.Field -import kotlin.reflect.full.allSuperclasses - - -@MiraiDebugAPI -actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String { - return (this::class.simpleName ?: "") + "#" + this::class.hashCode() + " {\n" + - this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") } - .distinctBy { it.name } - .filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" } - .joinToStringPrefixed( - prefix = prefix - ) { - it.isAccessible = true - if (filter != null) { - kotlin.runCatching { - if (!filter(it.name, it.get(this))) return@joinToStringPrefixed "" - } - } - it.name + "=" + kotlin.runCatching { - val value = it.get(this) - if (value == this) "" - else value.contentToString(prefix) - }.getOrElse { "" } - } + "\n$prefix}" -} - -internal fun Any.allFieldsFromSuperClassesMatching(classFilter: (Class) -> Boolean): Sequence { - return (this::class.java.takeIf(classFilter)?.declaredFields?.asSequence() ?: sequenceOf()) + this::class.allSuperclasses - .asSequence() - .map { it.java } - .filter(classFilter) - .flatMap { it.declaredFields.asSequence() } -} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt index 71ff9515b..ed02843f3 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformDatagramChannelAndroid.kt @@ -20,8 +20,6 @@ import java.nio.channels.DatagramChannel import java.nio.channels.ReadableByteChannel import java.nio.channels.WritableByteChannel -actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException - /** * 多平台适配的 DatagramChannel. */ diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt index f20543980..90711ef01 100644 --- a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt @@ -30,9 +30,16 @@ actual class PlatformSocket : Closeable { private lateinit var socket: Socket actual val isOpen: Boolean - get() = socket.isConnected + get() = + if (::socket.isInitialized) + socket.isConnected + else false - actual override fun close() = socket.close() + actual override fun close() { + if (::socket.isInitialized) { + socket.close() + } + } @PublishedApi internal lateinit var writeChannel: BufferedOutputStream @@ -73,7 +80,7 @@ actual class PlatformSocket : Closeable { } } - @UseExperimental(ExperimentalIoApi::class) + @OptIn(ExperimentalIoApi::class) actual suspend fun connect(serverHost: String, serverPort: Int) { withContext(Dispatchers.IO) { socket = Socket(serverHost, serverPort) diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt new file mode 100644 index 000000000..54f7f60ba --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/platformAndroid.kt @@ -0,0 +1,103 @@ +/* + * 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("NOTHING_TO_INLINE") + +package net.mamoe.mirai.utils + +import io.ktor.client.HttpClient +import io.ktor.client.engine.cio.CIO +import io.ktor.util.KtorExperimentalAPI +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.io.ByteArrayPool +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.net.InetAddress +import java.security.MessageDigest +import java.util.zip.Deflater +import java.util.zip.Inflater + + +/** + * 时间戳 + */ +actual val currentTimeMillis: Long get() = System.currentTimeMillis() + +@MiraiInternalAPI +actual object MiraiPlatformUtils { + actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val inflater = Inflater() + inflater.reset() + ByteArrayOutputStream().use { output -> + inflater.setInput(data, offset, length) + ByteArrayPool.useInstance { + while (!inflater.finished()) { + output.write(it, 0, inflater.inflate(it)) + } + } + + inflater.end() + return output.toByteArray() + } + } + + actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val deflater = Deflater() + deflater.setInput(data, offset, length) + deflater.finish() + + ByteArrayPool.useInstance { + return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() } + } + } + + actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() + } + + actual inline fun md5(str: String): ByteArray = md5(str.toByteArray()) + + /** + * Ktor HttpClient. 不同平台使用不同引擎. + */ + @OptIn(KtorExperimentalAPI::class) + actual val Http: HttpClient + get() = HttpClient(CIO) + + /** + * Localhost 解析 + */ + actual fun localIpAddress(): String = runCatching { + InetAddress.getLocalHost().hostAddress + }.getOrElse { "192.168.1.123" } + + fun md5(stream: InputStream): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + stream.readInSequence { + digest.update(it.toByte()) + } + return digest.digest() + } + + + private inline fun InputStream.readInSequence(block: (Int) -> Unit) { + var read: Int + while (this.read().also { read = it } != -1) { + block(read) + } + } +} \ No newline at end of file diff --git a/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/setVisible.kt b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/setVisible.kt new file mode 100644 index 000000000..ffecc5e9b --- /dev/null +++ b/mirai-core/src/androidMain/kotlin/net/mamoe/mirai/utils/setVisible.kt @@ -0,0 +1,8 @@ +package net.mamoe.mirai.utils + +import kotlin.reflect.KProperty1 +import kotlin.reflect.jvm.javaField + +internal actual fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? { + return this.javaField?.apply { isAccessible = true }?.get(receiver) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt index 4430c4514..a4cdb8f33 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/Bot.kt @@ -7,62 +7,74 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "FunctionName", "NOTHING_TO_INLINE", "UnusedImport") package net.mamoe.mirai +import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job -import kotlinx.io.OutputStream -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.IoBuffer -import kotlinx.io.core.use +import kotlinx.coroutines.io.ByteReadChannel +import kotlinx.coroutines.launch 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.message.MessageReceipt import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.network.BotNetworkHandler import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.transferTo +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic +import kotlin.jvm.JvmSynthetic + +/** + * 登录, 返回 [this] + */ +suspend inline fun B.alsoLogin(): B = also { login() } +// 任何人都能看到这个方法 /** * 机器人对象. 一个机器人实例登录一个 QQ 账号. * Mirai 为多账号设计, 可同时维护多个机器人. * - * 注: Bot 为全协程实现, 没有其他任务时若不使用 [awaitDisconnection], 主线程将会退出. + * 注: Bot 为全协程实现, 没有其他任务时若不使用 [join], 主线程将会退出. * - * @see Contact + * @see Contact 联系人 + * @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close]) */ -@UseExperimental(MiraiInternalAPI::class) -abstract class Bot : CoroutineScope { +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn(MiraiInternalAPI::class, LowLevelAPI::class) +expect abstract class Bot() : CoroutineScope, LowLevelBotAPIAccessor { companion object { /** * 复制一份此时的 [Bot] 实例列表. */ @JvmStatic - val instances: List> get() = BotImpl.instances.toList() + val instances: List> /** * 遍历每一个 [Bot] 实例 */ - inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) + inline fun forEachInstance(block: (Bot) -> Unit) /** * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] */ @JvmStatic - fun instanceWhose(qq: Long): Bot = BotImpl.instanceWhose(qq = qq) + fun getInstance(qq: Long): Bot } /** - * 账号信息 + * [Bot] 运行的 [Context]. + * + * 在 JVM 的默认实现为 `class ContextImpl : Context` + * 在 Android 实现为 `android.content.Context` */ - @MiraiInternalAPI - abstract val account: BotAccount + abstract val context: Context /** * QQ 号码. 实际类型为 uint @@ -74,7 +86,6 @@ abstract class Bot : CoroutineScope { */ @MiraiExperimentalAPI("还未支持") val nick: String - get() = TODO("bot 昵称获取") /** * 日志记录器 @@ -83,51 +94,46 @@ abstract class Bot : CoroutineScope { // region contacts + /** + * [QQ.id] 与 [Bot.uin] 相同的 [_lowLevelNewQQ] 实例 + */ abstract val selfQQ: QQ /** * 机器人的好友列表. 它将与服务器同步更新 */ + @Deprecated( + "use friends instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("this.friends") + ) abstract val qqs: ContactList /** - * 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException] + * 机器人的好友列表. 它将与服务器同步更新 */ - @Deprecated(message = "这个函数有歧义. 它获取的是好友, 却名为 getQQ", replaceWith = ReplaceWith("getFriend(id)")) - fun getQQ(id: Long): QQ = getFriend(id) + abstract val friends: ContactList /** * 获取一个好友或一个群. * 在一些情况下这可能会造成歧义. 请考虑后使用. */ - operator fun get(id: Long): Contact { - return this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException("contact id $id") - } + @Deprecated( + "use getFriend or getGroup instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException(\"contact id \$id\")") + ) + operator fun get(id: Long): Contact /** * 判断是否有这个 id 的好友或群. - * 在一些情况下这可能会造成歧义. 请考虑后使用. */ - operator fun contains(id: Long): Boolean { - return this.qqs.contains(id) || this.groups.contains(id) - } + operator fun contains(id: Long): Boolean /** * 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException] */ - fun getFriend(id: Long): QQ { - if (id == uin) return selfQQ - return qqs.delegate.getOrNull(id) - ?: throw NoSuchElementException("No such friend $id for bot ${this.uin}") - } - - /** - * 构造一个 [QQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]). - * - * [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job. - * 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭. - */ - abstract fun QQ(friendInfo: FriendInfo): QQ + fun getFriend(id: Long): QQ /** * 机器人加入的群列表. @@ -135,33 +141,11 @@ abstract class Bot : CoroutineScope { abstract val groups: ContactList /** - * 获取一个机器人加入的群. 若没有这个群, 则会抛出异常 [NoSuchElementException] - */ - fun getGroup(id: Long): Group { - return groups.delegate.getOrNull(id) - ?: throw NoSuchElementException("No such group $id for bot ${this.uin}") - } - - /** - * 获取群列表. 返回值前 32 bits 为 uin, 后 32 bits 为 groupCode - */ - abstract suspend fun queryGroupList(): Sequence - - /** - * 查询群资料. 获得的仅为当前时刻的资料. - * 请优先使用 [getGroup] 然后查看群资料. - */ - abstract suspend fun queryGroupInfo(id: Long): GroupInfo - - /** - * 查询群成员列表. - * 请优先使用 [getGroup], [Group.members] 查看群成员. + * 获取一个机器人加入的群. * - * 这个函数很慢. 请不要频繁使用. + * @throws NoSuchElementException 当不存在这个群时 */ - abstract suspend fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence - - // TODO 目前还不能构造群对象. 这将在以后支持 + fun getGroup(id: Long): Group // endregion @@ -173,31 +157,65 @@ abstract class Bot : CoroutineScope { abstract val network: BotNetworkHandler /** - * 挂起直到 [Bot] 下线. + * 挂起协程直到 [Bot] 下线. */ - suspend inline fun awaitDisconnection() = network.awaitDisconnection() + @JvmName("joinSuspend") + @JvmSynthetic + suspend inline fun join() /** * 登录, 或重新登录. - * 重新登录时不会再次拉取联系人列表. + * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. + * + * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. * * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] * * @throws LoginFailedException + * @see alsoLogin */ + @JvmName("loginSuspend") + @JvmSynthetic abstract suspend fun login() // endregion // region actions - @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) - abstract suspend fun Image.downloadAsByteArray(): ByteArray + /** + * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * + * @see Bot.recall (扩展函数) 接受参数 [MessageChain] + * @see _lowLevelRecallFriendMessage 低级 API + * @see _lowLevelRecallGroupMessage 低级 API + */ + @JvmName("recallSuspend") + @JvmSynthetic + abstract suspend fun recall(source: MessageSource) /** - * 将图片下载到内存中 (使用 [IoBuffer.Pool]) + * 获取图片下载链接 */ - abstract suspend fun Image.download(): ByteReadPacket + @JvmName("queryImageUrlSuspend") + @JvmSynthetic + abstract suspend fun queryImageUrl(image: Image): String + + /** + * 获取图片下载链接并开始下载. + * + * @see ByteReadChannel.copyAndClose + * @see ByteReadChannel.copyTo + */ + @JvmName("openChannelSuspend") + @JvmSynthetic + abstract suspend fun openChannel(image: Image): ByteReadChannel /** * 添加一个好友 @@ -205,49 +223,90 @@ abstract class Bot : CoroutineScope { * @param message 若需要验证请求时的验证消息. * @param remark 好友备注 */ + @JvmName("addFriendSuspend") + @JvmSynthetic + @MiraiExperimentalAPI("未支持") abstract suspend fun addFriend(id: Long, message: String? = null, remark: String? = null): AddFriendResult - /** - * 同意来自陌生人的加好友请求 - */ - abstract suspend fun approveFriendAddRequest(id: Long, remark: String?) - // endregion /** - * 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放. + * 关闭这个 [Bot], 立即取消 [Bot] 的 [kotlinx.coroutines.SupervisorJob]. + * 之后 [kotlinx.coroutines.isActive] 将会返回 `false`. * - * 注: 不可重新登录. 必须重新实例化一个 [Bot]. + * **注意:** 不可重新登录. 必须重新实例化一个 [Bot]. * * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭 + * + * @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭 */ abstract fun close(cause: Throwable? = null) - // region extensions - - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("getFriend(this.toLong())")) - fun Int.qq(): QQ = getFriend(this.toLong()) - - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("getFriend(this)")) - fun Long.qq(): QQ = getFriend(this) - - final override fun toString(): String { - return "Bot(${uin})" - } - - /** - * 需要调用者自行 close [output] - */ - suspend inline fun Image.downloadTo(output: OutputStream) = - download().use { input -> input.transferTo(output) } - - // endregion + @OptIn(LowLevelAPI::class, MiraiExperimentalAPI::class) + final override fun toString(): String } -inline fun Bot.containsFriend(id: Long): Boolean = this.qqs.contains(id) +/** + * 撤回这条消息. + * 根据 [message] 内的 [MessageSource] 进行相关判断. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * @see Bot.recall + */ +suspend inline fun Bot.recall(message: MessageChain) = this.recall(message[MessageSource]) + +/** + * 在一段时间后撤回这条消息. + * 将根据 [MessageSource.groupId] 判断消息是群消息还是好友消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @param coroutineContext 额外的 [CoroutineContext] + * @see recall + */ +fun Bot.recallIn( + source: MessageSource, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { + kotlinx.coroutines.delay(millis) + recall(source) +} + +/** + * 在一段时间后撤回这条消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @param coroutineContext 额外的 [CoroutineContext] + * @see recall + */ +fun Bot.recallIn( + message: MessageChain, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.launch(coroutineContext + CoroutineName("MessageRecall")) { + kotlinx.coroutines.delay(millis) + recall(message) +} + +/** + * 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放. + * + * 注: 不可重新登录. 必须重新实例化一个 [Bot]. + * + * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭 + */ +suspend inline fun Bot.closeAndJoin(cause: Throwable? = null) { + close(cause) + coroutineContext[Job]?.join() +} + +inline fun Bot.containsFriend(id: Long): Boolean = this.friends.contains(id) inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id) -inline fun Bot.getFriendOrNull(id: Long): QQ? = this.qqs.getOrNull(id) +inline fun Bot.getFriendOrNull(id: Long): QQ? = this.friends.getOrNull(id) inline fun Bot.getGroupOrNull(id: Long): Group? = this.groups.getOrNull(id) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt index 4a453234c..c8a87b302 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotAccount.kt @@ -12,8 +12,9 @@ package net.mamoe.mirai import kotlinx.io.core.toByteArray +import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.md5 +import net.mamoe.mirai.utils.MiraiPlatformUtils import kotlin.annotation.AnnotationTarget.* @MiraiInternalAPI @@ -23,9 +24,11 @@ data class BotAccount( */ @RawAccountIdUse val id: Long, + @MiraiExperimentalAPI + @MiraiInternalAPI val passwordMd5: ByteArray // md5 ) { - constructor(id: Long, passwordPlainText: String) : this(id, md5(passwordPlainText.toByteArray())) + constructor(id: Long, passwordPlainText: String) : this(id, MiraiPlatformUtils.md5(passwordPlainText.toByteArray())) override fun equals(other: Any?): Boolean { if (this === other) return true @@ -52,5 +55,5 @@ data class BotAccount( @MiraiInternalAPI @Retention(AnnotationRetention.SOURCE) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) -@Experimental(level = Experimental.Level.WARNING) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) annotation class RawAccountIdUse \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt index 7ad643351..b6250961a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotFactory.kt @@ -14,7 +14,6 @@ package net.mamoe.mirai import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.Context import kotlin.jvm.JvmName -import kotlin.jvm.JvmOverloads /** * 构造 [Bot] 的工厂. @@ -23,15 +22,49 @@ import kotlin.jvm.JvmOverloads * - `mirai-core-timpc`: `TIMPC` * - `mirai-core-qqandroid`: `QQAndroid` */ +@Suppress("INAPPLICABLE_JVM_NAME") interface BotFactory { /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ - fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot + @JvmName("newBot") + fun Bot( + context: Context, + qq: Long, + password: String, + configuration: BotConfiguration = BotConfiguration.Default + ): Bot + + /** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ + @JvmName("newBot") + fun Bot( + context: Context, + qq: Long, + passwordMd5: ByteArray, + configuration: BotConfiguration = BotConfiguration.Default + ): Bot } /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ -inline fun BotFactory.Bot(context: Context, qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot = - this.Bot(context, qq, password, BotConfiguration().apply(configuration)) \ No newline at end of file +inline fun BotFactory.Bot( + context: Context, + qq: Long, + password: String, + configuration: (BotConfiguration.() -> Unit) +): Bot = + this.Bot(context, qq, password, BotConfiguration().apply(configuration)) + +/** + * 使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +inline fun BotFactory.Bot( + context: Context, + qq: Long, + passwordMd5: ByteArray, + configuration: (BotConfiguration.() -> Unit) +): Bot = + this.Bot(context, qq, passwordMd5, BotConfiguration().apply(configuration)) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt deleted file mode 100644 index 89e929120..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotHelper.kt +++ /dev/null @@ -1,34 +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 - */ - -@file:JvmMultifileClass -@file:JvmName("BotHelperKt") -@file:Suppress("unused", "EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai - -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName - -/* - * 在 [Bot] 中的方法的捷径 - */ - -//Contacts -/** - * 登录, 返回 [this] - */ -suspend inline fun B.alsoLogin(): B = also { login() } - -/** - * 取得机器人的 QQ 号 - */ -@Deprecated(message = "Use this.uin instead", replaceWith = ReplaceWith("this.uin"), level = DeprecationLevel.WARNING) -inline val Bot.qqAccount: Long - get() = this.uin \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt index 23a913e50..1d269fcc5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/BotImpl.kt @@ -7,14 +7,13 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR") package net.mamoe.mirai import kotlinx.coroutines.* import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.broadcast -import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotReloginEvent import net.mamoe.mirai.event.subscribeAlways @@ -23,15 +22,15 @@ import net.mamoe.mirai.network.ForceOfflineException import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.closeAndJoin import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.logStacktrace import kotlin.coroutines.CoroutineContext /* * 泛型 N 不需要向外(接口)暴露. */ -@UseExperimental(MiraiExperimentalAPI::class) +@OptIn(MiraiExperimentalAPI::class) @MiraiInternalAPI abstract class BotImpl constructor( + context: Context, account: BotAccount, val configuration: BotConfiguration ) : Bot(), CoroutineScope { @@ -39,12 +38,15 @@ abstract class BotImpl constructor( override val coroutineContext: CoroutineContext = configuration.parentCoroutineContext + botJob + (configuration.parentCoroutineContext[CoroutineExceptionHandler] ?: CoroutineExceptionHandler { _, e -> logger.error("An exception was thrown under a coroutine of Bot", e) }) + override val context: Context by context.unsafeWeakRef() - @Suppress("CanBePrimaryConstructorProperty") // for logger + @OptIn(LowLevelAPI::class) + @Suppress("CanBePrimaryConstructorProperty", "OverridingDeprecatedMember") // for logger final override val account: BotAccount = account - @UseExperimental(RawAccountIdUse::class) + + @OptIn(RawAccountIdUse::class) override val uin: Long - get() = account.id + get() = this.account.id final override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) } init { @@ -59,7 +61,7 @@ abstract class BotImpl constructor( it.get()?.let(block) } - fun instanceWhose(qq: Long): Bot { + fun getInstance(qq: Long): Bot { instances.forEach { it.get()?.let { bot -> if (bot.uin == qq) { @@ -71,11 +73,6 @@ abstract class BotImpl constructor( } } - /** - * 可阻止事件广播 - */ - abstract fun onEvent(event: BotEvent): Boolean - // region network final override val network: N get() = _network @@ -87,21 +84,22 @@ abstract class BotImpl constructor( private val offlineListener: Listener = this.subscribeAlways { event -> when (event) { is BotOfflineEvent.Dropped -> { - bot.logger.info("Connection dropped or lost by server, retrying login") + if (!_network.isActive) { + return@subscribeAlways + } + bot.logger.info("Connection dropped by server or lost, retrying login") - var lastFailedException: Throwable? = null - repeat(configuration.reconnectionRetryTimes) { - try { - network.relogin() - logger.info("Reconnected successfully") - return@subscribeAlways - } catch (e: Throwable) { - lastFailedException = e + tryNTimesOrException(configuration.reconnectionRetryTimes) { tryCount -> + if (tryCount != 0) { delay(configuration.reconnectPeriodMillis) } - } - if (lastFailedException != null) { - throw lastFailedException!! + network.relogin(event.cause) + logger.info("Reconnected successfully") + BotReloginEvent(bot, event.cause).broadcast() + return@subscribeAlways + }?.let { + logger.info("Cannot reconnect") + throw it } } is BotOfflineEvent.Active -> { @@ -110,17 +108,21 @@ abstract class BotImpl constructor( } else { " with exception: " + event.cause.message } - bot.logger.info("Bot is closed manually$msg") - close(CancellationException(event.toString())) + bot.logger.info { "Bot is closed manually$msg" } + closeAndJoin(CancellationException(event.toString())) } is BotOfflineEvent.Force -> { - bot.logger.info("Connection occupied by another android device: ${event.message}") - close(ForceOfflineException(event.toString())) + bot.logger.info { "Connection occupied by another android device: ${event.message}" } + closeAndJoin(ForceOfflineException(event.toString())) } } } - final override suspend fun login() = reinitializeNetworkHandler(null) + final override suspend fun login() { + logger.info("Logging in...") + reinitializeNetworkHandler(null) + logger.info("Login successful") + } private suspend fun reinitializeNetworkHandler( cause: Throwable? @@ -143,20 +145,19 @@ abstract class BotImpl constructor( } suspend fun doInit() { - repeat(2) { - try { - _network.init() - return - } catch (e: Exception) { - e.logStacktrace() + tryNTimesOrException(2) { + if (it != 0) { + delay(3000) + logger.warning("Init failed. Retrying in 3s...") } - logger.warning("Init failed. Retrying in 3s...") - delay(3000) + _network.init() + }?.let { + network.logger.error(it) + logger.error("cannot init. some features may be affected") } - logger.error("cannot init. some features may be affected") } - logger.info("Initializing BotNetworkHandler") + // logger.info("Initializing BotNetworkHandler") if (::_network.isInitialized) { BotReloginEvent(this, cause).broadcast() @@ -172,21 +173,25 @@ abstract class BotImpl constructor( // endregion - @UseExperimental(MiraiInternalAPI::class) + @OptIn(MiraiInternalAPI::class) override fun close(cause: Throwable?) { + if (!this.botJob.isActive) { + // already cancelled + return + } kotlin.runCatching { if (cause == null) { + this.botJob.cancel() network.close() - this.botJob.complete() - offlineListener.complete() + offlineListener.cancel() } else { + this.botJob.cancel(CancellationException("bot cancelled", cause)) network.close(cause) - this.botJob.completeExceptionally(cause) - offlineListener.completeExceptionally(cause) + offlineListener.cancel(CancellationException("bot cancelled", cause)) } } groups.delegate.clear() - qqs.delegate.clear() + friends.delegate.clear() instances.removeIf { it.get()?.uin == this.uin } } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt index 80c2900bc..0e8030d3c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Contact.kt @@ -12,36 +12,49 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent +import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.WeakRefProperty +import net.mamoe.mirai.recall +import net.mamoe.mirai.recallIn +import net.mamoe.mirai.utils.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic /** * 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group]. * * @author Him188moe - */ -interface Contact : CoroutineScope { + */ // 不要删除多平台结构 !!! kotlin bug +@OptIn(MiraiInternalAPI::class, JavaHappyAPI::class) +@Suppress("INAPPLICABLE_JVM_NAME") +expect abstract class Contact() : CoroutineScope, ContactJavaHappyAPI { /** * 这个联系人所属 [Bot]. */ @WeakRefProperty - val bot: Bot + abstract val bot: Bot /** * 可以是 QQ 号码或者群号码. * * 对于 [QQ], `uin` 与 `id` 是相同的意思. * 对于 [Group], `groupCode` 与 `id` 是相同的意思. + * + * @see QQ.id + * @see Group.id */ - val id: Long + abstract val id: Long /** * 向这个对象发送消息. @@ -51,19 +64,25 @@ interface Contact : CoroutineScope { * * @throws EventCancelledException 当发送消息事件被取消 * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. */ - suspend fun sendMessage(message: MessageChain) + @JvmName("sendMessageSuspend") + @JvmSynthetic + abstract suspend fun sendMessage(message: MessageChain): MessageReceipt /** * 上传一个图片以备发送. - * TODO 群图片与好友图片在服务器上是通用的, 在 mirai 目前不通用. * * @see BeforeImageUploadEvent 图片发送前事件, cancellable * @see ImageUploadEvent 图片发送完成事件 * * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) */ - suspend fun uploadImage(image: ExternalImage): Image + @JvmName("uploadImageSuspend") + @JvmSynthetic + abstract suspend fun uploadImage(image: ExternalImage): OfflineImage /** * 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同. @@ -73,9 +92,58 @@ interface Contact : CoroutineScope { * 因为, [Member] 含义为群员, 必属于一个群. * 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人. */ - override fun equals(other: Any?): Boolean + abstract override fun equals(other: Any?): Boolean + + /** + * @return `bot.hashCode() * 31 + id.hashCode()` + */ + abstract override fun hashCode(): Int + + /** + * @return "QQ($id)" or "Group($id)" or "Member($id)" + */ + abstract override fun toString(): String } -suspend inline fun Contact.sendMessage(message: Message) = sendMessage(message.toChain()) +/** + * @see Bot.recall + */ +@MiraiExperimentalAPI +suspend inline fun Contact.recall(source: MessageChain) = this.bot.recall(source) -suspend inline fun Contact.sendMessage(plain: String) = sendMessage(plain.singleChain()) \ No newline at end of file +/** + * @see Bot.recall + */ +suspend inline fun Contact.recall(source: MessageSource) = this.bot.recall(source) + +/** + * @see Bot.recallIn + */ +@MiraiExperimentalAPI +fun Contact.recallIn( + message: MessageChain, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.bot.recallIn(message, millis, coroutineContext) + +/** + * @see Bot.recallIn + */ +fun Contact.recallIn( + source: MessageSource, + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext +): Job = this.bot.recallIn(source, millis, coroutineContext) + +/** + * @see Contact.sendMessage + */ +@Suppress("UNCHECKED_CAST") +suspend inline fun C.sendMessage(message: Message): MessageReceipt = + sendMessage(message.asMessageChain()) as? MessageReceipt ?: error("Internal class cast mistake") + +/** + * @see Contact.sendMessage + */ +@Suppress("UNCHECKED_CAST") +suspend inline fun C.sendMessage(plain: String): MessageReceipt = sendMessage(plain.toMessage()) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt index 5f459f5b9..a1ad294b5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/ContactList.kt @@ -16,8 +16,10 @@ import net.mamoe.mirai.utils.* /** * 只读联系人列表, lock-free 实现 + * + * @see ContactList.asSequence */ -@UseExperimental(MiraiInternalAPI::class) +@OptIn(MiraiInternalAPI::class) @Suppress("unused") class ContactList(@MiraiInternalAPI val delegate: LockFreeLinkedList) { /** @@ -40,8 +42,17 @@ class ContactList(@MiraiInternalAPI val delegate: LockFreeLinkedLis fun containsAll(elements: Collection): Boolean = elements.all { contains(it) } fun isEmpty(): Boolean = delegate.isEmpty() inline fun forEach(block: (C) -> Unit) = delegate.forEach(block) + fun first(): C { + forEach { return it } + throw NoSuchElementException() + } - override fun toString(): String = delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") + fun firstOrNull(): C? { + forEach { return it } + return null + } + + override fun toString(): String = delegate.asSequence().joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") } operator fun LockFreeLinkedList.get(id: Long): C { @@ -68,7 +79,7 @@ fun ContactList.toList(): List = toMutableList() /** * Collect all the elements into a [MutableList]. */ -@UseExperimental(MiraiInternalAPI::class) +@OptIn(MiraiInternalAPI::class) fun ContactList.toMutableList(): MutableList = this.delegate.toMutableList() /** @@ -79,7 +90,7 @@ fun ContactList.toSet(): Set = toMutableSet() /** * Collect all the elements into a [MutableSet]. */ -@UseExperimental(MiraiInternalAPI::class) +@OptIn(MiraiInternalAPI::class) fun ContactList.toMutableSet(): MutableSet = this.delegate.toMutableSet() /** @@ -87,7 +98,7 @@ fun ContactList.toMutableSet(): MutableSet = this.delegate.t * * Note that the sequence is dynamic, that is, elements are yielded atomically only when it is required */ -@UseExperimental(MiraiInternalAPI::class) +@OptIn(MiraiInternalAPI::class) fun ContactList.asSequence(): Sequence { return this.delegate.asSequence() } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt index a3c2a8ff2..15c7e97d7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Group.kt @@ -7,20 +7,30 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot import net.mamoe.mirai.data.MemberInfo 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.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineGroupImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic /** * 群. 在 QQ Android 中叫做 "Troop" */ -interface Group : Contact, CoroutineScope { +@Suppress("INAPPLICABLE_JVM_NAME") +expect abstract class Group() : Contact, CoroutineScope { /** * 群名称. * @@ -30,7 +40,7 @@ interface Group : Contact, CoroutineScope { * @see MemberPermissionChangeEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ - var name: String + abstract var name: String /** * 入群公告, 没有时为空字符串. * @@ -39,7 +49,7 @@ interface Group : Contact, CoroutineScope { * @see GroupEntranceAnnouncementChangeEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ - var entranceAnnouncement: String + abstract var entranceAnnouncement: String /** * 全体禁言状态. `true` 为开启. * @@ -48,7 +58,7 @@ interface Group : Contact, CoroutineScope { * @see GroupMuteAllEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ - var isMuteAll: Boolean + abstract var isMuteAll: Boolean /** * 坦白说状态. `true` 为允许. * @@ -57,7 +67,7 @@ interface Group : Contact, CoroutineScope { * @see GroupAllowConfessTalkEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ - var isConfessTalkEnabled: Boolean + abstract var isConfessTalkEnabled: Boolean /** * 允许群员邀请好友入群的状态. `true` 为允许 * @@ -66,70 +76,85 @@ interface Group : Contact, CoroutineScope { * @see GroupAllowMemberInviteEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ - var isAllowMemberInvite: Boolean + abstract var isAllowMemberInvite: Boolean /** * 自动加群审批 */ - val isAutoApproveEnabled: Boolean + abstract val isAutoApproveEnabled: Boolean /** * 匿名聊天 */ - val isAnonymousChatEnabled: Boolean + abstract val isAnonymousChatEnabled: Boolean /** * 同为 groupCode, 用户看到的群号码. */ - override val id: Long + abstract override val id: Long /** - * 群主 + * 群主. + * + * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 */ - val owner: Member + abstract val owner: Member + + /** + * [Bot] 在群内的 [Member] 实例 + */ + @MiraiExperimentalAPI + abstract val botAsMember: Member /** * 机器人被禁言还剩余多少秒 * - * @see BotMuteEvent - * @see isBotMuted + * @see BotMuteEvent 机器人被禁言事件 + * @see isBotMuted 判断机器人是否正在被禁言 */ - val botMuteRemaining: Int + abstract val botMuteRemaining: Int /** * 机器人在这个群里的权限 * - * **MiraiExperimentalAPI**: 在未来可能会被修改 + * @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限 + * @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator] * - * @see BotGroupPermissionChangeEvent + * @see BotGroupPermissionChangeEvent 机器人群员修改 */ - @MiraiExperimentalAPI - val botPermission: MemberPermission + abstract val botPermission: MemberPermission + /** + * 群头像下载链接. + */ + val avatarUrl: String /** * 群成员列表, 不含机器人自己, 含群主. * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新 */ - val members: ContactList + abstract val members: ContactList /** * 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException] */ - operator fun get(id: Long): Member + abstract operator fun get(id: Long): Member /** * 获取群成员实例, 不存在则 null */ - fun getOrNull(id: Long): Member? + abstract fun getOrNull(id: Long): Member? /** * 检查此 id 的群成员是否存在 */ - operator fun contains(id: Long): Boolean + abstract operator fun contains(id: Long): Boolean /** * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 */ - suspend fun quit(): Boolean + @JvmName("quitSuspend") + @JvmSynthetic + @MiraiExperimentalAPI("还未支持") + abstract suspend fun quit(): Boolean /** * 构造一个 [Member]. @@ -138,51 +163,50 @@ interface Group : Contact, CoroutineScope { @MiraiExperimentalAPI("dangerous") @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName") @JvmName("newMember") - fun Member(memberInfo: MemberInfo): Member + abstract fun Member(memberInfo: MemberInfo): Member + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmName("sendMessageSuspend") + @JvmSynthetic + abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + abstract override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage companion object { + // don't @JvmStatic: JDK 1.8 required + fun calculateGroupUinByGroupCode(groupCode: Long): Long - /** - * by @kar98k - */ // don't @JvmStatic: JDK 1.8 required - fun calculateGroupUinByGroupCode(groupCode: Long): Long { - var left: Long = groupCode / 1000000L - - when (left) { - in 0..10 -> left += 202 - in 11..19 -> left += 480 - 11 - in 20..66 -> left += 2100 - 20 - in 67..156 -> left += 2010 - 67 - in 157..209 -> left += 2147 - 157 - in 210..309 -> left += 4100 - 210 - in 310..499 -> left += 3800 - 310 - } - - return left * 1000000L + groupCode % 1000000L - } - - fun calculateGroupCodeByGroupUin(groupUin: Long): Long { - var left: Long = groupUin / 1000000L - - when (left) { - in 0 + 202..10 + 202 -> left -= 202 - in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11 - in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20 - in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67 - in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157 - in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210 - in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310 - } - - return left * 1000000L + groupUin % 1000000L - } + fun calculateGroupCodeByGroupUin(groupUin: Long): Long } @MiraiExperimentalAPI - fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" + fun toFullString(): String } /** * 返回机器人是否正在被禁言 + * + * @see Group.botMuteRemaining 剩余禁言时间 */ val Group.isBotMuted: Boolean get() = this.botMuteRemaining != 0 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt index 66e8f2ca3..d088d8224 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Member.kt @@ -1,7 +1,7 @@ /* * Copyright 2020 Mamoe Technologies and contributors. * - * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * 此源代码的使用受 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 @@ -12,50 +12,76 @@ package net.mamoe.mirai.contact import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.WeakRefProperty +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic import kotlin.time.Duration import kotlin.time.ExperimentalTime /** * 群成员. - */ -interface Member : QQ, Contact { + */ // 不要删除多平台结构, kotlin bug +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn(MiraiInternalAPI::class, JavaHappyAPI::class) +expect abstract class Member() : MemberJavaHappyAPI { /** * 所在的群. */ @WeakRefProperty - val group: Group + abstract val group: Group /** * 成员的权限, 动态更新. + * + * @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发. */ - val permission: MemberPermission + abstract val permission: MemberPermission /** - * 群名片. 可能为空. 修改时将会触发事件 + * 群名片. 可能为空. + * + * 管理员和群主都可修改任何人(包括群主)的群名片. * * 在修改时将会异步上传至服务器. * - * @see [groupCardOrNick] 获取非空群名片或昵称 + * @see [nameCardOrNick] 获取非空群名片或昵称 * - * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 + * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. * @throws PermissionDeniedException 无权限修改时 */ - var nameCard: String + abstract var nameCard: String /** - * 群头衔 + * 群头衔. + * + * 仅群主可以修改群头衔. * * 在修改时将会异步上传至服务器. * - * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 + * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. * @throws PermissionDeniedException 无权限修改时 */ - var specialTitle: String + abstract var specialTitle: String /** - * 禁言 + * 被禁言剩余时长. 单位为秒. + * + * @see isMuted 判断改成员是否处于禁言状态 + * @see mute 设置禁言 + * @see unmute 取消禁言 + */ + abstract val muteTimeRemaining: Int + + /** + * 禁言. + * + * QQ 中最小操作和显示的时间都是一分钟. + * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. * * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. * @return 机器人无权限时返回 `false` @@ -67,28 +93,43 @@ interface Member : QQ, Contact { * @see MemberMuteEvent 成员被禁言事件 * @throws PermissionDeniedException 无权限修改时 */ - suspend fun mute(durationSeconds: Int) + @JvmName("muteSuspend") + @JvmSynthetic + abstract suspend fun mute(durationSeconds: Int) /** * 解除禁言. * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * * @see MemberUnmuteEvent 成员被取消禁言事件. * @throws PermissionDeniedException 无权限修改时 */ - suspend fun unmute() + @JvmName("unmuteSuspend") + @JvmSynthetic + abstract suspend fun unmute() /** * 踢出该成员. * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * * @see MemberLeaveEvent.Kick 成员被踢出事件. * @throws PermissionDeniedException 无权限修改时 */ - suspend fun kick(message: String = "") + @JvmName("kickSuspend") + @JvmSynthetic + abstract suspend fun kick(message: String = "") /** * 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true */ - override fun equals(other: Any?): Boolean + abstract override fun equals(other: Any?): Boolean + + /** + * @return `bot.hashCode() * 31 + id.hashCode()` + */ + abstract override fun hashCode(): Int } /** @@ -96,7 +137,14 @@ interface Member : QQ, Contact { * * 若 [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [QQ.nick] */ -val Member.groupCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick +val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick + +/** + * 判断改成员是否处于禁言状态. + */ +fun Member.isMuted(): Boolean { + return muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt() +} @ExperimentalTime suspend inline fun Member.mute(duration: Duration) { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/MemberPermission.kt similarity index 83% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/MemberPermission.kt index ac1137744..22dc88a09 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/Permission.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/MemberPermission.kt @@ -9,6 +9,7 @@ package net.mamoe.mirai.contact +import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.MiraiExperimentalAPI @@ -68,7 +69,6 @@ inline fun Member.isAdministrator(): Boolean = this.permission.isAdministrator() inline fun Member.isOperator(): Boolean = this.permission.isOperator() - /** * 权限不足 */ @@ -77,7 +77,12 @@ expect class PermissionDeniedException : IllegalStateException { constructor(message: String?) } -@UseExperimental(MiraiExperimentalAPI::class) +/** + * 要求 [Bot] 在这个群里的权限为 [required], 否则抛出异常 [PermissionDeniedException] + * + * @throws PermissionDeniedException + */ +@OptIn(MiraiExperimentalAPI::class) inline fun Group.checkBotPermission( required: MemberPermission, lazyMessage: () -> String = { @@ -89,7 +94,12 @@ inline fun Group.checkBotPermission( } } -@UseExperimental(MiraiExperimentalAPI::class) +/** + * 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator], 否则抛出异常 [PermissionDeniedException] + * + * @throws PermissionDeniedException + */ +@OptIn(MiraiExperimentalAPI::class) inline fun Group.checkBotPermissionOperator( lazyMessage: () -> String = { "Permission denied: required ${MemberPermission.ADMINISTRATOR} or ${MemberPermission.OWNER}, got actual $botPermission for $bot in group $id" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt index 986c00072..b5c41001f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/QQ.kt @@ -7,7 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.contact @@ -16,7 +16,19 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.data.FriendNameRemark import net.mamoe.mirai.data.PreviousNameList import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.event.events.BeforeImageUploadEvent +import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent +import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent +import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic /** * QQ 对象. @@ -30,28 +42,32 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI * * @author Him188moe */ -interface QQ : Contact, CoroutineScope { - /** - * QQ 号码 - */ - override val id: Long - - /** - * 昵称 - */ - val nick: String - +@Suppress("INAPPLICABLE_JVM_NAME") +expect abstract class QQ() : Contact, CoroutineScope { /** * 请求头像下载链接 */ // @MiraiExperimentalAPI //suspend fun queryAvatar(): AvatarLink + /** + * QQ 号码 + */ + abstract override val id: Long + /** + * 昵称 + */ + abstract val nick: String /** * 查询用户资料 */ @MiraiExperimentalAPI("还未支持") - suspend fun queryProfile(): Profile + abstract suspend fun queryProfile(): Profile + + /** + * 头像下载链接 + */ + val avatarUrl: String /** * 查询曾用名. @@ -61,11 +77,39 @@ interface QQ : Contact, CoroutineScope { * - 共同群内的群名片 */ @MiraiExperimentalAPI("还未支持") - suspend fun queryPreviousNameList(): PreviousNameList + abstract suspend fun queryPreviousNameList(): PreviousNameList /** * 查询机器人账号给这个人设置的备注 */ @MiraiExperimentalAPI("还未支持") - suspend fun queryRemark(): FriendNameRemark + abstract suspend fun queryRemark(): FriendNameRemark + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmSynthetic + @JvmName("sendMessageSuspend") + abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/jvmHappy.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/jvmHappy.kt new file mode 100644 index 000000000..3653f8fac --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/contact/jvmHappy.kt @@ -0,0 +1,27 @@ +/* + * 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.contact + +import net.mamoe.mirai.JavaHappyAPI +import net.mamoe.mirai.utils.MiraiInternalAPI + +/** + * [Contact] 中为了让 `Java` 更容易调用的 API + */ +@MiraiInternalAPI +@JavaHappyAPI +expect abstract class ContactJavaHappyAPI + +/** + * [Member] 中为了让 `Java` 更容易调用的 API + */ +@MiraiInternalAPI +@JavaHappyAPI +expect abstract class MemberJavaHappyAPI : QQ \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt index 0a594a4b0..1e6f4e66e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/GroupInfo.kt @@ -5,7 +5,7 @@ import net.mamoe.mirai.Bot /** * 群资料. * - * 通过 [Bot.queryGroupInfo] 得到 + * 通过 [Bot._lowLevelQueryGroupInfo] 得到 */ interface GroupInfo { /** @@ -62,4 +62,15 @@ interface GroupInfo { * 机器人被禁言还剩时间, 秒. */ val botMuteRemaining: Int + + /* + /** + * 机器人的头衔 + */ + val botSpecialTitle: String + + /** + * 机器人的昵称 + */ + val botNameCard: String*/ } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/ImageLink.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/ImageLink.kt deleted file mode 100644 index 0d444382c..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/ImageLink.kt +++ /dev/null @@ -1,27 +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.data - -import io.ktor.client.request.get -import io.ktor.util.KtorExperimentalAPI -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.readBytes -import net.mamoe.mirai.utils.Http - -interface ImageLink { - /** - * 原图 - */ - val original: String - - suspend fun downloadAsByteArray(): ByteArray = download().readBytes() - - suspend fun download(): ByteReadPacket = Http.get(original) -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt index 3ed39ef10..37e6ba4b0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/MemberInfo.kt @@ -17,4 +17,6 @@ interface MemberInfo : FriendInfo { val permission: MemberPermission val specialTitle: String + + val muteTimestamp: Int } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt index 958118257..4772c9aa7 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/Packet.kt @@ -29,8 +29,15 @@ object NoPacket : Packet { /** * PacketFactory 可以一次解析多个包出来. 它们将会被分别广播. */ -open class MultiPacket

(internal val delegate: List

) : List

by delegate, Packet { - override fun toString(): String { - return "MultiPacket<${this.firstOrNull()?.let { it::class.simpleName } ?: "?"}>" - } +interface MultiPacket : Packet, Iterable

+ +open class MultiPacketByIterable(internal val delegate: Iterable

) : MultiPacket

, + Iterable

by delegate { + override fun toString(): String = "MultiPacketByIterable" +} + +open class MultiPacketBySequence(internal val delegate: Sequence

) : MultiPacket

{ + override operator fun iterator(): Iterator

= delegate.iterator() + + override fun toString(): String = "MultiPacketBySequence" } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt similarity index 89% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt index 3aff7e809..3ce170194 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribable.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Event.kt @@ -11,8 +11,6 @@ package net.mamoe.mirai.event -import net.mamoe.mirai.BotImpl -import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.internal.broadcastInternal import net.mamoe.mirai.utils.MiraiInternalAPI @@ -22,7 +20,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI * 若监听这个类, 监听器将会接收所有事件的广播. * * @see subscribeAlways - * @see subscribeWhile + * @see subscribeOnce * * @see subscribeMessages * @@ -68,14 +66,11 @@ abstract class AbstractCancellableEvent : Event, CancellableEvent { /** * 广播一个事件的唯一途径. */ -@UseExperimental(MiraiInternalAPI::class) +@OptIn(MiraiInternalAPI::class) suspend fun E.broadcast(): E = apply { if (this is BroadcastControllable && !this.shouldBroadcast) { return@apply } - if (this is BotEvent && !(this.bot as BotImpl<*>).onEvent(this)) { - return@apply - } this@broadcast.broadcastInternal() // inline, no extra cost } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt deleted file mode 100644 index fd6887e85..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/Subscribers.kt +++ /dev/null @@ -1,207 +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.event - -import kotlinx.coroutines.CompletableJob -import kotlinx.coroutines.CoroutineExceptionHandler -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.GlobalScope -import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.internal.Handler -import net.mamoe.mirai.event.internal.subscribeInternal -import net.mamoe.mirai.utils.MiraiInternalAPI -import kotlin.coroutines.CoroutineContext - -/* - * 该文件为所有的订阅事件的方法. - */ - -/** - * 订阅者的状态 - */ -enum class ListeningStatus { - /** - * 表示继续监听 - */ - LISTENING, - - /** - * 表示已停止 - */ - STOPPED -} - -/** - * 事件监听器. - * 由 [subscribe] 等方法返回. - * - * 取消监听: [complete] - */ -interface Listener : CompletableJob { - suspend fun onEvent(event: E): ListeningStatus -} - -// region 顶层方法 创建当前 coroutineContext 下的子 Job - -/** - * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. - * 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行. - * - * 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听. - * 或 [Listener.complete] 后结束. - * - * 这个函数返回 [Listener], 它是一个 [CompletableJob]. 请注意它除非被 [Listener.complete] 或 [Listener.cancel], 则不会完成. - * 例: - * ```kotlin - * runBlocking { // this: CoroutineScope - * subscribe { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob - * } - * foo() - * ``` - * `runBlocking` 不会结束, 也就是下一行 `foo()` 不会被执行. 直到监听时创建的 `Listener` 被停止. - * - * - * 要创建一个全局都存在的监听, 即守护协程, 请在 [GlobalScope] 下调用本函数: - * ```kotlin - * GlobalScope.subscribe { /* 一些处理 */ } - * ``` - * - * - * 要创建一个仅在某个机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]): - * ```kotlin - * bot.subscribe { /* 一些处理 */ } - * ``` - * - * - * 事件处理时的 [CoroutineContext] 为调用本函数时的 [receiver][this] 的 [CoroutineScope.coroutineContext]. - * 因此: - * - 事件处理时抛出的异常将会在 [this] 的 [CoroutineExceptionHandler] 中处理 - * 若 [this] 没有 [CoroutineExceptionHandler], 则在事件广播方的 [CoroutineExceptionHandler] 处理 - * 若均找不到, 则会触发 logger warning. - * - 事件处理时抛出异常不会停止监听器. - * - 建议在事件处理中, 即 [handler] 里处理异常, 或在 [this] 指定 [CoroutineExceptionHandler]. - * - * - * **注意:** 事件处理是 `suspend` 的, 请严格控制 JVM 阻塞方法的使用. 若致事件处理阻塞, 则会导致一些逻辑无法进行. - * - * // TODO: 2020/2/13 在 bot 下监听时同时筛选对应 bot 实例 - * - * @see subscribeMessages 监听消息 DSL - * @see subscribeGroupMessages 监听群消息 DSL - * @see subscribeFriendMessages 监听好友消息 DSL - */ -@UseExperimental(MiraiInternalAPI::class) -inline fun CoroutineScope.subscribe(crossinline handler: suspend E.(E) -> ListeningStatus): Listener = - E::class.subscribeInternal(Handler { it.handler(it); }) - -/** - * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. - * 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行. - * - * 仅当 [Listener.complete] 或 [Listener.cancel] 时结束. - * - * @see subscribe 获取更多说明 - */ -@UseExperimental(MiraiInternalAPI::class) -inline fun CoroutineScope.subscribeAlways(crossinline listener: suspend E.(E) -> Unit): Listener = - E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.LISTENING }) - -/** - * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. - * 仅在第一次 [事件广播][Event.broadcast] 时, [listener] 会被执行. - * - * 在这之前, 可通过 [Listener.complete] 来停止监听. - * - * @see subscribe 获取更多说明 - */ -@UseExperimental(MiraiInternalAPI::class) -inline fun CoroutineScope.subscribeOnce(crossinline listener: suspend E.(E) -> Unit): Listener = - E::class.subscribeInternal(Handler { it.listener(it); ListeningStatus.STOPPED }) - -/** - * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. - * 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行, 直到 [listener] 的返回值 [equals] 于 [valueIfStop] - * - * 可在任意时刻通过 [Listener.complete] 来停止监听. - * - * @see subscribe 获取更多说明 - */ -@UseExperimental(MiraiInternalAPI::class) -inline fun CoroutineScope.subscribeUntil(valueIfStop: T, crossinline listener: suspend E.(E) -> T): Listener = - E::class.subscribeInternal(Handler { if (it.listener(it) == valueIfStop) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) - -/** - * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. - * 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行, - * 如果 [listener] 的返回值 [equals] 于 [valueIfContinue], 则继续监听, 否则停止 - * - * 可在任意时刻通过 [Listener.complete] 来停止监听. - * - * @see subscribe 获取更多说明 - */ -@UseExperimental(MiraiInternalAPI::class) -inline fun CoroutineScope.subscribeWhile(valueIfContinue: T, crossinline listener: suspend E.(E) -> T): Listener = - E::class.subscribeInternal(Handler { if (it.listener(it) != valueIfContinue) ListeningStatus.STOPPED else ListeningStatus.LISTENING }) - -// endregion - -// region ListenerBuilder DSL - -/* -/** - * 监听构建器. 可同时进行多种方式的监听 - * - * ```kotlin - * FriendMessageEvent.subscribe { - * always{ - * it.reply("永远发生") - * } - * - * untilFalse { - * it.reply("你发送了 ${it.event}") - * it.event eq "停止" - * } - * } - * ``` - */ -@ListenersBuilderDsl -@Suppress("MemberVisibilityCanBePrivate", "unused") -inline class ListenerBuilder( - @PublishedApi internal inline val handlerConsumer: CoroutineCoroutineScope.(Listener) -> Unit -) { - fun CoroutineCoroutineScope.handler(listener: suspend E.(E) -> ListeningStatus) { - handlerConsumer(Handler { it.listener(it) }) - } - - fun CoroutineCoroutineScope.always(listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.LISTENING } - - fun CoroutineCoroutineScope.until(until: T, listener: suspend E.(E) -> T) = - handler { if (listener(it) == until) ListeningStatus.STOPPED else ListeningStatus.LISTENING } - - fun CoroutineCoroutineScope.untilFalse(listener: suspend E.(E) -> Boolean) = until(false, listener) - fun CoroutineCoroutineScope.untilTrue(listener: suspend E.(E) -> Boolean) = until(true, listener) - fun CoroutineCoroutineScope.untilNull(listener: suspend E.(E) -> Any?) = until(null, listener) - - - fun CoroutineCoroutineScope.`while`(until: T, listener: suspend E.(E) -> T) = - handler { if (listener(it) !== until) ListeningStatus.STOPPED else ListeningStatus.LISTENING } - - fun CoroutineCoroutineScope.whileFalse(listener: suspend E.(E) -> Boolean) = `while`(false, listener) - fun CoroutineCoroutineScope.whileTrue(listener: suspend E.(E) -> Boolean) = `while`(true, listener) - fun CoroutineCoroutineScope.whileNull(listener: suspend E.(E) -> Any?) = `while`(null, listener) - - - fun CoroutineCoroutineScope.once(listener: suspend E.(E) -> Unit) = handler { listener(it); ListeningStatus.STOPPED } -} - -@DslMarker -annotation class ListenersBuilderDsl -*/ -// endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt index a855e40b2..bfa3eb9f0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/BotEvents.kt @@ -7,6 +7,8 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("unused") + package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot @@ -15,6 +17,8 @@ import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.AbstractCancellableEvent import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.CancellableEvent +import net.mamoe.mirai.event.events.ImageUploadEvent.Failed +import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.utils.ExternalImage @@ -52,12 +56,13 @@ sealed class BotOfflineEvent : BotEvent { /** * 被挤下线 */ - data class Force(override val bot: Bot, val title: String, val message: String) : BotOfflineEvent(), Packet, BotPassiveEvent + data class Force(override val bot: Bot, val title: String, val message: String) : BotOfflineEvent(), Packet, + BotPassiveEvent /** * 被服务器断开或因网络问题而掉线 */ - data class Dropped(override val bot: Bot) : BotOfflineEvent(), Packet, BotPassiveEvent + data class Dropped(override val bot: Bot, val cause: Throwable?) : BotOfflineEvent(), Packet, BotPassiveEvent } /** @@ -88,6 +93,67 @@ sealed class MessageSendEvent : BotEvent, BotActiveEvent, AbstractCancellableEve ) : MessageSendEvent(), CancellableEvent } +/** + * 消息撤回事件. 可是任意消息被任意人撤回. + */ +sealed class MessageRecallEvent : BotEvent { + /** + * 消息原发送人 + */ + abstract val authorId: Long + + /** + * 消息 id. + * @see MessageSource.id + */ + abstract val messageId: Long + + /** + * 原发送时间 + */ + abstract val messageTime: Int // seconds + + /** + * 好友消息撤回事件, 暂不支持解析. + */ + data class FriendRecall( + override val bot: Bot, + override val messageId: Long, + override val messageTime: Int, + /** + * 撤回操作人, 可能为 [Bot.uin] 或好友的 [QQ.id] + */ + val operator: Long + ) : MessageRecallEvent(), Packet { + override val authorId: Long + get() = bot.uin + } + + data class GroupRecall( + override val bot: Bot, + override val authorId: Long, + override val messageId: Long, + override val messageTime: Int, + /** + * 操作人. 为 null 时则为 [Bot] 操作. + */ + override val operator: Member?, + override val group: Group + ) : MessageRecallEvent(), GroupOperableEvent, Packet +} + +@OptIn(MiraiExperimentalAPI::class) +val MessageRecallEvent.GroupRecall.author: Member + get() = if (authorId == bot.uin) group.botAsMember else group[authorId] + +val MessageRecallEvent.FriendRecall.isByBot: Boolean get() = this.operator == bot.uin + +val MessageRecallEvent.isByBot: Boolean + get() = when (this) { + is MessageRecallEvent.FriendRecall -> this.isByBot + is MessageRecallEvent.GroupRecall -> (this as GroupOperableEvent).isByBot + } + // endregion // region 图片 @@ -105,6 +171,9 @@ data class BeforeImageUploadEvent( /** * 图片上传完成 + * + * @see Succeed + * @see Failed */ sealed class ImageUploadEvent : BotEvent, BotActiveEvent, AbstractCancellableEvent() { abstract val target: Contact @@ -194,7 +263,7 @@ data class GroupNameChangeEvent( override val origin: String, override val new: String, override val group: Group, - val isByBot: Boolean + val isByBot: Boolean // 无法获取操作人 ) : GroupSettingChangeEvent, Packet /** @@ -207,8 +276,8 @@ data class GroupEntranceAnnouncementChangeEvent( /** * 操作人. 为 null 时则是机器人操作 */ - val operator: Member? -) : GroupSettingChangeEvent, Packet + override val operator: Member? +) : GroupSettingChangeEvent, Packet, GroupOperableEvent /** @@ -221,8 +290,9 @@ data class GroupMuteAllEvent( /** * 操作人. 为 null 时则是机器人操作 */ - val operator: Member? -) : GroupSettingChangeEvent, Packet + override val operator: Member? +) : GroupSettingChangeEvent, Packet, GroupOperableEvent + /** * 群 "匿名聊天" 功能状态改变. 此事件广播前修改就已经完成. @@ -234,8 +304,9 @@ data class GroupAllowAnonymousChatEvent( /** * 操作人. 为 null 时则是机器人操作 */ - val operator: Member? -) : GroupSettingChangeEvent, Packet + override val operator: Member? +) : GroupSettingChangeEvent, Packet, GroupOperableEvent + /** * 群 "坦白说" 功能状态改变. 此事件广播前修改就已经完成. @@ -244,7 +315,7 @@ data class GroupAllowConfessTalkEvent( override val origin: Boolean, override val new: Boolean, override val group: Group, - val isByBot: Boolean + val isByBot: Boolean // 无法获取操作人 ) : GroupSettingChangeEvent, Packet /** @@ -257,8 +328,9 @@ data class GroupAllowMemberInviteEvent( /** * 操作人. 为 null 时则是机器人操作 */ - val operator: Member? -) : GroupSettingChangeEvent, Packet + override val operator: Member? +) : GroupSettingChangeEvent, Packet, GroupOperableEvent + // endregion @@ -284,13 +356,21 @@ sealed class MemberLeaveEvent : GroupMemberEvent { /** * 操作人. 为 null 则是机器人操作 */ - val operator: Member? - ) : MemberLeaveEvent(), Packet + override val operator: Member? + ) : MemberLeaveEvent(), Packet, GroupOperableEvent { + override fun toString(): String { + return "MemberLeaveEvent.Kick(member=$member, operator=$operator)" + } + } /** * 成员主动离开 */ - data class Quit(override val member: Member) : MemberLeaveEvent() + data class Quit(override val member: Member) : MemberLeaveEvent(), Packet { + override fun toString(): String { + return "MemberLeaveEvent.Quit(member=$member)" + } + } } // endregion @@ -316,8 +396,8 @@ data class MemberCardChangeEvent( /** * 操作人. 为 null 时则是机器人操作. 可能与 [member] 引用相同, 此时为群员自己修改. */ - val operator: Member? -) : GroupMemberEvent + override val operator: Member? +) : GroupMemberEvent, GroupOperableEvent /** * 群头衔改动. 一定为群主操作 @@ -333,8 +413,15 @@ data class MemberSpecialTitleChangeEvent( */ val new: String, - override val member: Member -) : GroupMemberEvent + override val member: Member, + + /** + * 操作人. + * 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改. + * 为 null 时则是机器人操作. + */ + override val operator: Member? +) : GroupMemberEvent, GroupOperableEvent // endregion @@ -364,8 +451,8 @@ data class MemberMuteEvent( /** * 操作人. 为 null 则为机器人操作 */ - val operator: Member? -) : GroupMemberEvent, Packet + override val operator: Member? +) : GroupMemberEvent, Packet, GroupOperableEvent /** * 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人 @@ -375,8 +462,8 @@ data class MemberUnmuteEvent( /** * 操作人. 为 null 则为机器人操作 */ - val operator: Member? -) : GroupMemberEvent, Packet + override val operator: Member? +) : GroupMemberEvent, Packet, GroupOperableEvent // endregion diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt index dd55a8bd5..8acf68fd9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/events/types.kt @@ -14,6 +14,7 @@ import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.event.Event +import net.mamoe.mirai.utils.MiraiExperimentalAPI /** * 有关一个 [Bot] 的事件 @@ -51,6 +52,31 @@ interface GroupMemberEvent : GroupEvent { get() = member.group } +/** + * 可由 [Member] 或 [Bot] 操作的事件 + * @see isByBot + * @see operatorOrBot + */ +interface GroupOperableEvent : GroupEvent { + /** + * 操作人, 为 `null` 时为 [Bot] 操作 + */ + val operator: Member? +} + +/** + * 是否由 [Bot] 操作 + */ +val GroupOperableEvent.isByBot: Boolean get() = operator == null + +/** + * 当操作人为 [Member] 时获取这个 [Member], + * 当操作人为 [Bot] 时获取 [Group.botAsMember] + */ +@OptIn(MiraiExperimentalAPI::class) +val GroupOperableEvent.operatorOrBot: Member + get() = this.operator ?: this.group.botAsMember + /** * 有关好友的事件 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt index c72941469..1d068c826 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/internal/InternalEventListeners.kt @@ -9,14 +9,14 @@ package net.mamoe.mirai.event.internal -import kotlinx.atomicfu.atomic import kotlinx.coroutines.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.EventDisabled import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.utils.* -import net.mamoe.mirai.utils.io.logStacktrace import kotlin.coroutines.CoroutineContext import kotlin.coroutines.coroutineContext import kotlin.jvm.JvmField @@ -32,8 +32,13 @@ fun , E : Event> KClass.subscribeInternal(listener: L): L @PublishedApi @Suppress("FunctionName") -internal fun CoroutineScope.Handler(handler: suspend (E) -> ListeningStatus): Handler { - return Handler(coroutineContext[Job], coroutineContext, handler) +internal fun CoroutineScope.Handler( + coroutineContext: CoroutineContext, + handler: suspend (E) -> ListeningStatus +): Handler { + @OptIn(ExperimentalCoroutinesApi::class) // don't remove + val context = this.newCoroutineContext(coroutineContext) + return Handler(context[Job], context, handler) } private inline fun inline(block: () -> Unit) = block() @@ -45,7 +50,7 @@ internal class Handler @PublishedApi internal constructor(parentJob: Job?, private val subscriberContext: CoroutineContext, @JvmField val handler: suspend (E) -> ListeningStatus) : Listener, CompletableJob by Job(parentJob) { - @UseExperimental(MiraiDebugAPI::class) + @OptIn(MiraiDebugAPI::class) override suspend fun onEvent(event: E): ListeningStatus { if (isCompleted || isCancelled) return ListeningStatus.STOPPED if (!isActive) return ListeningStatus.LISTENING @@ -60,8 +65,8 @@ internal class Handler MiraiLogger.warning( """Event processing: An exception occurred but no CoroutineExceptionHandler found, either in coroutineContext from Handler job, or in subscriberContext""".trimIndent() + , e ) - e.logStacktrace("Event processing(No CoroutineExceptionHandler found)") } // this.complete() // do not `completeExceptionally`, otherwise parentJob will fai`l. // ListeningStatus.STOPPED @@ -70,6 +75,9 @@ internal class Handler ListeningStatus.LISTENING } } + + @MiraiInternalAPI + override val lock: Mutex = Mutex() } /** @@ -77,7 +85,32 @@ internal class Handler */ internal fun KClass.listeners(): EventListeners = EventListenerManager.get(this) -internal class EventListeners : LockFreeLinkedList>() +internal class EventListeners(clazz: KClass) : LockFreeLinkedList>() { + @Suppress("UNCHECKED_CAST", "UNSUPPORTED", "NO_REFLECTION_IN_CLASS_PATH") + val supertypes: Set> by lazy { + val supertypes = mutableSetOf>() + + fun addSupertypes(clazz: KClass) { + clazz.supertypes.forEach { + val classifier = it.classifier as? KClass + if (classifier != null) { + supertypes.add(classifier) + addSupertypes(classifier) + } + } + } + addSupertypes(clazz) + + supertypes + } +} + +internal expect class MiraiAtomicBoolean(initial: Boolean) { + + fun compareAndSet(expect: Boolean, update: Boolean): Boolean + + var value: Boolean +} /** * 管理每个事件 class 的 [EventListeners]. @@ -88,7 +121,8 @@ internal object EventListenerManager { private val registries = LockFreeLinkedList>() - private val lock = atomic(false) + // 不要用 atomicfu. 在 publish 后会出现 VerifyError + private val lock: MiraiAtomicBoolean = MiraiAtomicBoolean(false) @Suppress("UNCHECKED_CAST", "BooleanLiteralArgument") internal tailrec fun get(clazz: KClass): EventListeners { @@ -98,10 +132,10 @@ internal object EventListenerManager { } } if (lock.compareAndSet(false, true)) { - val registry = Registry(clazz, EventListeners()) + val registry = Registry(clazz as KClass, EventListeners(clazz)) registries.addLast(registry) lock.value = false - return registry.listeners as EventListeners + return registry.listeners } return get(clazz) } @@ -109,32 +143,29 @@ internal object EventListenerManager { // inline: NO extra Continuation @Suppress("UNCHECKED_CAST") -internal suspend inline fun Event.broadcastInternal() { - if (EventDisabled) return +internal suspend inline fun Event.broadcastInternal() = coroutineScope { + if (EventDisabled) return@coroutineScope EventLogger.info { "Event broadcast: $this" } - callAndRemoveIfRequired(this::class.listeners()) - - var supertypes = this::class.supertypes - while (true) { - val superSubscribableType = supertypes.firstOrNull { - it.classifier as? KClass != null - } - - superSubscribableType?.let { - callAndRemoveIfRequired((it.classifier as KClass).listeners()) - } - - supertypes = (superSubscribableType?.classifier as? KClass<*>)?.supertypes ?: return + val listeners = this@broadcastInternal::class.listeners() + callAndRemoveIfRequired(this@broadcastInternal, listeners) + listeners.supertypes.forEach { + callAndRemoveIfRequired(this@broadcastInternal, it.listeners()) } } -private suspend inline fun E.callAndRemoveIfRequired(listeners: EventListeners) { +@OptIn(MiraiInternalAPI::class) +private fun CoroutineScope.callAndRemoveIfRequired(event: E, listeners: EventListeners) { // atomic foreach - listeners.forEach { - if (it.onEvent(this) == ListeningStatus.STOPPED) { - listeners.remove(it) // atomic remove + listeners.forEachNode { node -> + launch { + val listener = node.nodeValue + listener.lock.withLock { + if (!node.isRemoved() && listener.onEvent(event) == ListeningStatus.STOPPED) { + listeners.remove(listener) // atomic remove + } + } } } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt new file mode 100644 index 000000000..0aab16c3f --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/linear.kt @@ -0,0 +1,112 @@ +/* + * 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.event + +import kotlinx.coroutines.* +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext + +/** + * 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值. + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 + * + * @see subscribingGetAsync 本函数的异步版本 + */ +@MiraiExperimentalAPI +suspend inline fun subscribingGet( + timeoutMillis: Long = -1, + noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常 +): R { + require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } + return subscribingGetOrNull(timeoutMillis, filter) ?: error("timeout subscribingGet") +} + +/** + * 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值. + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 + * + * @see subscribingGetAsync 本函数的异步版本 + */ +@MiraiExperimentalAPI +suspend inline fun subscribingGetOrNull( + timeoutMillis: Long = -1, + noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常 +): R? { + require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } + var result: R? = null + var resultThrowable: Throwable? = null + + if (timeoutMillis == -1L) { + @Suppress("DuplicatedCode") // for better performance + coroutineScope { + var listener: Listener? = null + listener = this.subscribe { + val value = try { + filter.invoke(this, it) + } catch (e: Exception) { + resultThrowable = e + return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() } + } + + if (value != null) { + result = value + return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() } + } else return@subscribe ListeningStatus.LISTENING + } + } + } else { + withTimeoutOrNull(timeoutMillis) { + var listener: Listener? = null + @Suppress("DuplicatedCode") // for better performance + listener = this.subscribe { + val value = try { + filter.invoke(this, it) + } catch (e: Exception) { + resultThrowable = e + return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() } + } + + if (value != null) { + result = value + return@subscribe ListeningStatus.STOPPED.also { listener!!.complete() } + } else return@subscribe ListeningStatus.LISTENING + } + } + } + resultThrowable?.let { throw it } + return result +} + +/** + * 异步监听这个事件, 并尝试从这个事件中获取一个值. + * + * 若 [filter] 抛出的异常将会被传递给 [Deferred.await] 抛出. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @param coroutineContext 额外的 [CoroutineContext] + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 + */ +@MiraiExperimentalAPI +inline fun CoroutineScope.subscribingGetAsync( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + timeoutMillis: Long = -1, + noinline filter: E.(E) -> R? // 不要 crossinline: crossinline 后 stacktrace 会不正常 +): Deferred = this.async(coroutineContext) { + subscribingGet(timeoutMillis, filter) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt similarity index 64% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt index 5fe31559e..7426807f5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/MessageSubscribers.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscribeMessages.kt @@ -11,7 +11,10 @@ package net.mamoe.mirai.event +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.isAdministrator import net.mamoe.mirai.contact.isOperator @@ -23,22 +26,28 @@ import net.mamoe.mirai.message.data.Message import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext /** * 订阅来自所有 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 */ -@UseExperimental(ExperimentalContracts::class) -@MessageDsl -inline fun CoroutineScope.subscribeMessages(crossinline listeners: MessageSubscribersBuilder>.() -> R): R { +@OptIn(ExperimentalContracts::class) +fun CoroutineScope.subscribeMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + listeners: MessageSubscribersBuilder>.() -> R +): R { // contract 可帮助 IDE 进行类型推断. 无实际代码作用. contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return MessageSubscribersBuilder { messageListener: MessageListener> -> - // subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [listener] - // listener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块. - subscribeAlways { + // subscribeAlways 即注册一个监听器. 这个监听器收到消息后就传递给 [messageListener] + // messageListener 即为 DSL 里 `contains(...) { }`, `startsWith(...) { }` 的代码块. + subscribeAlways(coroutineContext) { messageListener.invoke(this, this.message.toString()) // this.message.toString() 即为 messageListener 中 it 接收到的值 } @@ -47,15 +56,19 @@ inline fun CoroutineScope.subscribeMessages(crossinline listeners: MessageSu /** * 订阅来自所有 [Bot] 的所有群消息事件 + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 */ -@UseExperimental(ExperimentalContracts::class) -@MessageDsl -inline fun CoroutineScope.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder.() -> R): R { +@OptIn(ExperimentalContracts::class) +fun CoroutineScope.subscribeGroupMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + listeners: MessageSubscribersBuilder.() -> R +): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return MessageSubscribersBuilder { listener -> - subscribeAlways { + subscribeAlways(coroutineContext) { listener(this, this.message.toString()) } }.run(listeners) @@ -63,15 +76,19 @@ inline fun CoroutineScope.subscribeGroupMessages(crossinline listeners: Mess /** * 订阅来自所有 [Bot] 的所有好友消息事件 + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 */ -@UseExperimental(ExperimentalContracts::class) -@MessageDsl -inline fun CoroutineScope.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder.() -> R): R { +@OptIn(ExperimentalContracts::class) +fun CoroutineScope.subscribeFriendMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + listeners: MessageSubscribersBuilder.() -> R +): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return MessageSubscribersBuilder { listener -> - subscribeAlways { + subscribeAlways(coroutineContext) { listener(this, this.message.toString()) } }.run(listeners) @@ -79,15 +96,19 @@ inline fun CoroutineScope.subscribeFriendMessages(crossinline listeners: Mes /** * 订阅来自这个 [Bot] 的所有联系人的消息事件. 联系人可以是任意群或任意好友或临时会话. + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 */ -@UseExperimental(ExperimentalContracts::class) -@MessageDsl -inline fun Bot.subscribeMessages(crossinline listeners: MessageSubscribersBuilder>.() -> R): R { +@OptIn(ExperimentalContracts::class) +fun Bot.subscribeMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + listeners: MessageSubscribersBuilder>.() -> R +): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return MessageSubscribersBuilder> { listener -> - this.subscribeAlways { + this.subscribeAlways(coroutineContext) { listener(this, this.message.toString()) } }.run(listeners) @@ -95,15 +116,21 @@ inline fun Bot.subscribeMessages(crossinline listeners: MessageSubscribersBu /** * 订阅来自这个 [Bot] 的所有群消息事件 + * + * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext] + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 */ -@UseExperimental(ExperimentalContracts::class) -@MessageDsl -inline fun Bot.subscribeGroupMessages(crossinline listeners: MessageSubscribersBuilder.() -> R): R { +@OptIn(ExperimentalContracts::class) +fun Bot.subscribeGroupMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + listeners: MessageSubscribersBuilder.() -> R +): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return MessageSubscribersBuilder { listener -> - this.subscribeAlways { + this.subscribeAlways(coroutineContext) { listener(this, this.message.toString()) } }.run(listeners) @@ -111,20 +138,50 @@ inline fun Bot.subscribeGroupMessages(crossinline listeners: MessageSubscrib /** * 订阅来自这个 [Bot] 的所有好友消息事件. + * + * @see CoroutineScope.incoming 打开一个指定事件的接收通道 */ -@UseExperimental(ExperimentalContracts::class) -@MessageDsl -inline fun Bot.subscribeFriendMessages(crossinline listeners: MessageSubscribersBuilder.() -> R): R { +@OptIn(ExperimentalContracts::class) +fun Bot.subscribeFriendMessages( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + listeners: MessageSubscribersBuilder.() -> R +): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return MessageSubscribersBuilder { listener -> - this.subscribeAlways { + this.subscribeAlways(coroutineContext) { listener(this, this.message.toString()) } }.run(listeners) } +/** + * 打开一个指定事件的接收通道 + * + * @param capacity 同 [Channel] 的参数, 参见 [Channel.Factory] 中的常量. + * + * @see capacity 默认无限大小. 详见 [Channel.Factory] 中的常量 [Channel.UNLIMITED], [Channel.CONFLATED], [Channel.RENDEZVOUS]. + * 请谨慎使用 [Channel.RENDEZVOUS]: 在 [Channel] 未被 [接收][Channel.receive] 时他将会阻塞事件处理 + * + * @see subscribeFriendMessages + * @see subscribeMessages + * @see subscribeGroupMessages + */ +inline fun CoroutineScope.incoming( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + capacity: Int = Channel.UNLIMITED +): ReceiveChannel { + return Channel(capacity).apply { + val listener = this@incoming.subscribeAlways(coroutineContext) { + send(this) + } + this.invokeOnClose { + listener.cancel(CancellationException("ReceiveChannel closed", it)) + } + } +} + /** * 消息事件的处理器. @@ -193,8 +250,40 @@ class MessageSubscribersBuilder>( operator fun invoke(onEvent: MessageListener): Listener { return content(filter, onEvent) } + + infix fun reply(toReply: String): Listener { + return content(filter) { reply(toReply) } + } + + infix fun reply(message: Message): Listener { + return content(filter) { reply(message) } + } + + infix fun reply(replier: (@MessageDsl suspend T.(String) -> Any?)): Listener { + return content(filter) { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + executeAndReply(replier) + } + } + + infix fun quoteReply(toReply: String): Listener { + return content(filter) { quoteReply(toReply) } + } + + infix fun quoteReply(message: Message): Listener { + return content(filter) { quoteReply(message) } + } + + infix fun quoteReply(replier: (@MessageDsl suspend T.(String) -> Any?)): Listener { + return content(filter) { + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + executeAndQuoteReply(replier) + } + } } + // TODO: 2020/3/6 这些 lambda 都应该 crossinline, 但这会导致异常时的 stacktrace 不准确 (Kotlin 1.3.70) 待 Kotlin 修复此问题后恢复 inline 结构 + /** * 无任何触发条件. */ @@ -224,11 +313,11 @@ class MessageSubscribersBuilder>( * @param ignoreCase `true` 则不区分大小写 */ @MessageDsl - inline fun case( + fun case( equals: String, ignoreCase: Boolean = false, trim: Boolean = true, - crossinline onEvent: @MessageDsl suspend T.(String) -> Unit + onEvent: @MessageDsl suspend T.(String) -> Unit ): Listener { val toCheck = if (trim) equals.trim() else equals return content({ (if (trim) it.trim() else it).equals(toCheck, ignoreCase = ignoreCase) }, { @@ -244,14 +333,14 @@ class MessageSubscribersBuilder>( content { sub in it } /** - * 如果消息内容包含 [sub] + * 如果消息内容包含 [sub] 中的任意一个元素 */ @MessageDsl - inline fun contains( + fun contains( sub: String, ignoreCase: Boolean = false, trim: Boolean = true, - crossinline onEvent: MessageListener + onEvent: MessageListener ): Listener { return if (trim) { val toCheck = sub.trim() @@ -265,6 +354,72 @@ class MessageSubscribersBuilder>( } } + /** + * 如果消息内容包含 [sub] + */ + @MessageDsl + fun containsAny(vararg sub: String): ListeningFilter = + content { sub.any { item -> item in it } } + + /** + * 如果消息内容包含 [sub] 中的任意一个元素 + */ + @MessageDsl + fun containsAny( + vararg sub: String, + ignoreCase: Boolean = false, + trim: Boolean = true, + onEvent: MessageListener + ): Listener { + return if (trim) { + val list = sub.map { it.trim() } + content({ + list.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } + }, { + onEvent(this, this.message.toString().trim()) + }) + } else { + content({ + sub.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } + }, { + onEvent(this, this.message.toString()) + }) + } + } + + /** + * 如果消息内容包含 [sub] + */ + @MessageDsl + fun containsAll(vararg sub: String): ListeningFilter = + content { sub.all { item -> item in it } } + + /** + * 如果消息内容包含 [sub] 中的任意一个元素 + */ + @MessageDsl + fun containsAll( + vararg sub: String, + ignoreCase: Boolean = false, + trim: Boolean = true, + onEvent: MessageListener + ): Listener { + return if (trim) { + val list = sub.map { it.trim() } + content({ + list.all { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } + }, { + onEvent(this, this.message.toString().trim()) + }) + } else { + content({ + sub.all { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } + }, { + onEvent(this, this.message.toString()) + }) + } + } + /** * 如果消息的前缀是 [prefix] */ @@ -281,11 +436,11 @@ class MessageSubscribersBuilder>( * 如果消息的前缀是 [prefix] */ @MessageDsl - inline fun startsWith( + fun startsWith( prefix: String, removePrefix: Boolean = true, trim: Boolean = true, - crossinline onEvent: @MessageDsl suspend T.(String) -> Unit + onEvent: @MessageDsl suspend T.(String) -> Unit ): Listener { return if (trim) { val toCheck = prefix.trim() @@ -312,11 +467,11 @@ class MessageSubscribersBuilder>( * 如果消息的结尾是 [suffix] */ @MessageDsl - inline fun endsWith( + fun endsWith( suffix: String, removeSuffix: Boolean = true, trim: Boolean = true, - crossinline onEvent: @MessageDsl suspend T.(String) -> Unit + onEvent: @MessageDsl suspend T.(String) -> Unit ): Listener { return if (trim) { val toCheck = suffix.trim() @@ -343,7 +498,7 @@ class MessageSubscribersBuilder>( * 如果是这个人发的消息. 消息目前只会是群消息 */ @MessageDsl - inline fun sentBy(name: String, crossinline onEvent: MessageListener): Listener = + fun sentBy(name: String, onEvent: MessageListener): Listener = content({ this is GroupMessage && this.senderName == name }, onEvent) /** @@ -357,14 +512,14 @@ class MessageSubscribersBuilder>( * 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */ @MessageDsl - inline fun sentBy(qq: Long, crossinline onEvent: MessageListener): Listener = + fun sentBy(qq: Long, onEvent: MessageListener): Listener = content({ this.sender.id == qq }, onEvent) /** * 如果是好友发来的消息 */ @MessageDsl - inline fun sentByFriend(crossinline onEvent: MessageListener): Listener = + fun sentByFriend(onEvent: MessageListener): Listener = content({ this is FriendMessage }) { onEvent(this as FriendMessage, it) } @@ -386,7 +541,7 @@ class MessageSubscribersBuilder>( * 如果是管理员或群主发的消息 */ @MessageDsl - inline fun sentByOperator(crossinline onEvent: MessageListener): Listener = + fun sentByOperator(onEvent: MessageListener): Listener = content({ this is GroupMessage && this.sender.isOperator() }, onEvent) /** @@ -400,7 +555,7 @@ class MessageSubscribersBuilder>( * 如果是管理员发的消息 */ @MessageDsl - inline fun sentByAdministrator(crossinline onEvent: MessageListener): Listener = + fun sentByAdministrator(onEvent: MessageListener): Listener = content({ this is GroupMessage && this.sender.isAdministrator() }, onEvent) /** @@ -414,7 +569,7 @@ class MessageSubscribersBuilder>( * 如果是群主发的消息 */ @MessageDsl - inline fun sentByOwner(crossinline onEvent: MessageListener): Listener = + fun sentByOwner(onEvent: MessageListener): Listener = content({ this is GroupMessage && this.sender.isOwner() }, onEvent) /** @@ -428,7 +583,7 @@ class MessageSubscribersBuilder>( * 如果是来自这个群的消息, 就执行 [onEvent] */ @MessageDsl - inline fun sentFrom(groupId: Long, crossinline onEvent: MessageListener): Listener = + fun sentFrom(groupId: Long, onEvent: MessageListener): Listener = content({ this is GroupMessage && this.group.id == groupId }) { onEvent(this as GroupMessage, it) } @@ -438,14 +593,14 @@ class MessageSubscribersBuilder>( */ @MessageDsl inline fun has(): ListeningFilter = - content { message.any { it::class == M::class } } + content { message.any { it is M } } /** * 如果消息内容包含 [M] 类型的 [Message], 就执行 [onEvent] */ @MessageDsl - inline fun has(crossinline onEvent: MessageListener): Listener = - content({ message.any { it::class == M::class } }, onEvent) + inline fun has(noinline onEvent: MessageListener): Listener = + content({ message.any { it is M } }, onEvent) /** * 如果 [filter] 返回 `true` @@ -458,31 +613,53 @@ class MessageSubscribersBuilder>( * 如果 [filter] 返回 `true` 就执行 `onEvent` */ @MessageDsl - inline fun content(crossinline filter: T.(String) -> Boolean, crossinline onEvent: MessageListener): Listener = + fun content( + filter: T.(String) -> Boolean, + onEvent: MessageListener + ): Listener = subscriber { if (filter(this, it)) onEvent(this, it) } /** - * 如果消息内容可由正则表达式匹配([Regex.matchEntire]), 就执行 `onEvent` + * 如果消息内容可由正则表达式匹配([Regex.matchEntire]) */ @MessageDsl fun matching(regex: Regex): ListeningFilter = content { regex.matchEntire(it) != null } /** - * 如果 [filter] 返回 `true` 就执行 `onEvent` + * 如果消息内容可由正则表达式匹配([Regex.matchEntire]), 就执行 `onEvent` */ @MessageDsl - inline fun matching(regex: Regex, crossinline onEvent: MessageListener): Listener = - content({ regex.matchEntire(it) != null }, onEvent) + fun matching(regex: Regex, onEvent: @MessageDsl suspend T.(MatchResult) -> Unit): Listener = + always { + val find = regex.matchEntire(it) ?: return@always + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply { + onEvent.invoke(this, find) + } + } + + /** + * 如果消息内容可由正则表达式查找([Regex.find]) + */ + @MessageDsl + fun finding(regex: Regex): ListeningFilter = + content { regex.find(it) != null } /** * 如果消息内容可由正则表达式查找([Regex.find]), 就执行 `onEvent` */ @MessageDsl - fun finding(regex: Regex): ListeningFilter = - content { regex.find(it) != null } + fun finding(regex: Regex, onEvent: @MessageDsl suspend T.(MatchResult) -> Unit): Listener = + always { + val find = regex.find(it) ?: return@always + @Suppress("DSL_SCOPE_VIOLATION_WARNING") + this.executeAndReply { + onEvent.invoke(this, find) + } + } /** @@ -500,7 +677,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun String.containsReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener = + infix fun String.containsReply(replier: @MessageDsl suspend T.(String) -> Any?): Listener = content({ this@containsReply in it }, { @Suppress("DSL_SCOPE_VIOLATION_WARNING") this.executeAndReply(replier) @@ -514,11 +691,14 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun Regex.matchingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener = - content({ this@matchingReply.matchEntire(it) != null }, { + infix fun Regex.matchingReply(replier: @MessageDsl suspend T.(MatchResult) -> Any?): Listener = + always { + val find = this@matchingReply.matchEntire(it) ?: return@always @Suppress("DSL_SCOPE_VIOLATION_WARNING") - this.executeAndReply(replier) - }) + this.executeAndReply { + replier.invoke(this, find) + } + } /** * 若消息内容可由正则表达式查找([Regex.find]), 则执行 [replier] 并将其返回值回复给发信对象. @@ -528,11 +708,14 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun Regex.findingReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener = - content({ this@findingReply.find(it) != null }, { + infix fun Regex.findingReply(replier: @MessageDsl suspend T.(MatchResult) -> Any?): Listener = + always { + val find = this@findingReply.find(it) ?: return@always @Suppress("DSL_SCOPE_VIOLATION_WARNING") - this.executeAndReply(replier) - }) + this.executeAndReply { + replier.invoke(this, find) + } + } /** * 不考虑空格, 若消息内容以 [this] 开始则执行 [replier] 并将其返回值回复给发信对象. @@ -548,7 +731,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他类型则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun String.startsWithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener { + infix fun String.startsWithReply(replier: @MessageDsl suspend T.(String) -> Any?): Listener { val toCheck = this.trimStart() return content({ it.trim().startsWith(toCheck) }, { @Suppress("DSL_SCOPE_VIOLATION_WARNING") @@ -572,7 +755,7 @@ class MessageSubscribersBuilder>( * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl - inline infix fun String.endsWithReply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener { + infix fun String.endsWithReply(replier: @MessageDsl suspend T.(String) -> Any?): Listener { val toCheck = this.trimEnd() return content({ it.trim().endsWith(toCheck) }, { @Suppress("DSL_SCOPE_VIOLATION_WARNING") @@ -595,7 +778,7 @@ class MessageSubscribersBuilder>( } @MessageDsl - inline infix fun String.reply(crossinline replier: @MessageDsl suspend T.(String) -> Any?): Listener { + infix fun String.reply(replier: @MessageDsl suspend T.(String) -> Any?): Listener { val toCheck = this.trim() return content({ it.trim() == toCheck }, { @Suppress("DSL_SCOPE_VIOLATION_WARNING") @@ -617,6 +800,17 @@ class MessageSubscribersBuilder>( } } + @PublishedApi + @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE") // false positive + internal suspend inline fun T.executeAndQuoteReply(replier: suspend T.(String) -> Any?) { + when (val message = replier(this, this.message.toString())) { + is Message -> this.quoteReply(message) + is Unit -> { + + } + else -> this.quoteReply(message.toString()) + } + } /* 易产生迷惑感 fun replyCase(equals: String, trim: Boolean = true, replier: MessageReplier) = case(equals, trim) { reply(replier(this)) } fun replyContains(value: String, replier: MessageReplier) = content({ value in it }) { replier(this) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt new file mode 100644 index 000000000..c652d88d5 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/event/subscriber.kt @@ -0,0 +1,239 @@ +/* + * 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.event + +import kotlinx.coroutines.CompletableJob +import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.sync.Mutex +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.events.BotEvent +import net.mamoe.mirai.event.internal.Handler +import net.mamoe.mirai.event.internal.subscribeInternal +import net.mamoe.mirai.utils.MiraiInternalAPI +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.jvm.JvmName + +/* + * 该文件为所有的订阅事件的方法. + */ + +/** + * 订阅者的状态 + */ +enum class ListeningStatus { + /** + * 表示继续监听 + */ + LISTENING, + + /** + * 表示已停止 + */ + STOPPED +} + +/** + * 事件监听器. + * 由 [subscribe] 等方法返回. + * + * 取消监听: [complete] + */ +interface Listener : CompletableJob { + /** + * [onEvent] 的锁 + */ + @MiraiInternalAPI + val lock: Mutex + + suspend fun onEvent(event: E): ListeningStatus +} + +// region 顶层方法 创建当前 coroutineContext 下的子 Job + +/** + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行. + * + * 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听. + * 或 [Listener.complete] 后结束. + * + * 这个函数返回 [Listener], 它是一个 [CompletableJob]. 请注意它除非被 [Listener.complete] 或 [Listener.cancel], 则不会完成. + * 例: + * ```kotlin + * runBlocking { // this: CoroutineScope + * subscribe { /* 一些处理 */ } // 返回 Listener, 即 CompletableJob + * } + * foo() + * ``` + * `runBlocking` 不会结束, 也就是下一行 `foo()` 不会被执行. 直到监听时创建的 `Listener` 被停止. + * + * + * 要创建一个仅在某个机器人在线时的监听, 请在 [Bot] 下调用本函数 (因为 [Bot] 也实现 [CoroutineScope]). + * 这种方式创建的监听会自动筛选 [Bot]. + * ```kotlin + * bot1.subscribe { /* 只会处理来自 bot1 的事件 */ } + * ``` + * + * + * 要创建一个全局都存在的监听, 即守护协程, 请在 [GlobalScope] 下调用本函数: + * ```kotlin + * GlobalScope.subscribe { /* 会收到来自全部 Bot 的事件和与 Bot 不相关的事件 */ } + * ``` + * + * + * 事件处理时的 [CoroutineContext] 为调用本函数时的 [receiver][this] 的 [CoroutineScope.coroutineContext]. + * 因此: + * - 事件处理时抛出的异常将会在 [this] 的 [CoroutineExceptionHandler] 中处理 + * 若 [this] 没有 [CoroutineExceptionHandler], 则在事件广播方的 [CoroutineExceptionHandler] 处理 + * 若均找不到, 则会触发 logger warning. + * - 事件处理时抛出异常不会停止监听器. + * - 建议在事件处理中 (即 [handler] 里) 处理异常, + * 或在 [this] 的 [CoroutineScope.coroutineContext] 中添加 [CoroutineExceptionHandler]. + * + * + * **注意:** 事件处理是 `suspend` 的, 请规范处理 JVM 阻塞方法. + * + * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext] + * + * @see subscribingGet 监听一个事件, 并尝试从这个事件中获取一个值. + * @see subscribingGetAsync 异步监听一个事件, 并尝试从这个事件中获取一个值. + * + * @see subscribeAlways 一直监听 + * @see subscribeOnce 只监听一次 + * + * @see subscribeMessages 监听消息 DSL + * @see subscribeGroupMessages 监听群消息 DSL + * @see subscribeFriendMessages 监听好友消息 DSL + */ +@OptIn(MiraiInternalAPI::class) +inline fun CoroutineScope.subscribe( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline handler: suspend E.(E) -> ListeningStatus +): Listener = + E::class.subscribeInternal(Handler(coroutineContext) { it.handler(it); }) + +/** + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行. + * + * 可在任意时候通过 [Listener.complete] 来主动停止监听. + * [Bot] 被关闭后事件监听会被 [取消][Listener.cancel]. + * + * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext] + * + * @see subscribe 获取更多说明 + */ +@OptIn(MiraiInternalAPI::class, ExperimentalContracts::class) +inline fun CoroutineScope.subscribeAlways( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline listener: suspend E.(E) -> Unit +): Listener { + contract { + callsInPlace(listener, InvocationKind.UNKNOWN) + } + return E::class.subscribeInternal(Handler(coroutineContext) { it.listener(it); ListeningStatus.LISTENING }) +} + +/** + * 在指定的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 仅在第一次 [事件广播][Event.broadcast] 时, [listener] 会被执行. + * + * 可在任意时候通过 [Listener.complete] 来主动停止监听. + * [Bot] 被关闭后事件监听会被 [取消][Listener.cancel]. + * + * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext] + * + * @see subscribe 获取更多说明 + */ +@OptIn(MiraiInternalAPI::class) +inline fun CoroutineScope.subscribeOnce( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline listener: suspend E.(E) -> Unit +): Listener = + E::class.subscribeInternal(Handler(coroutineContext) { it.listener(it); ListeningStatus.STOPPED }) + + +// +// 以下为带筛选 Bot 的监听 +// + + +/** + * 在 [Bot] 的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行, + * 当 [handler] 返回 [ListeningStatus.STOPPED] 时停止监听 + * + * 可在任意时候通过 [Listener.complete] 来主动停止监听. + * [Bot] 被关闭后事件监听会被 [取消][Listener.cancel]. + * + * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext] + * + * @see subscribe 获取更多说明 + */ +@JvmName("subscribeAlwaysForBot") +@OptIn(MiraiInternalAPI::class) +inline fun Bot.subscribe( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline handler: suspend E.(E) -> ListeningStatus +): Listener = + E::class.subscribeInternal(Handler(coroutineContext) { if (it.bot === this) it.handler(it) else ListeningStatus.LISTENING }) + + +/** + * 在 [Bot] 的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 每当 [事件广播][Event.broadcast] 时, [listener] 都会被执行. + * + * 可在任意时候通过 [Listener.complete] 来主动停止监听. + * [Bot] 被关闭后事件监听会被 [取消][Listener.cancel]. + * + * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext] + * + * @see subscribe 获取更多说明 + */ +@JvmName("subscribeAlwaysForBot1") +@OptIn(MiraiInternalAPI::class) +inline fun Bot.subscribeAlways( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline listener: suspend E.(E) -> Unit +): Listener { + return E::class.subscribeInternal(Handler(coroutineContext) { if (it.bot === this) it.listener(it); ListeningStatus.LISTENING }) +} + +/** + * 在 [Bot] 的 [CoroutineScope] 下订阅所有 [E] 及其子类事件. + * 仅在第一次 [事件广播][Event.broadcast] 时, [listener] 会被执行. + * + * 可在任意时候通过 [Listener.complete] 来主动停止监听. + * [Bot] 被关闭后事件监听会被 [取消][Listener.cancel]. + * + * @param coroutineContext 给事件监听协程的额外的 [CoroutineContext] + * + * @see subscribe 获取更多说明 + */ +@JvmName("subscribeOnceForBot2") +@OptIn(MiraiInternalAPI::class) +inline fun Bot.subscribeOnce( + coroutineContext: CoroutineContext = EmptyCoroutineContext, + noinline listener: suspend E.(E) -> Unit +): Listener = + E::class.subscribeInternal(Handler(coroutineContext) { + if (it.bot === this) { + it.listener(it) + ListeningStatus.STOPPED + } else ListeningStatus.LISTENING + }) + +// endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/javaHappy.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/javaHappy.kt new file mode 100644 index 000000000..31de9e48c --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/javaHappy.kt @@ -0,0 +1,33 @@ +/* + * 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 + +import net.mamoe.mirai.utils.MiraiInternalAPI + +/** + * 表明这个 API 是为了让 Java 使用者调用更方便. + */ +@MiraiInternalAPI +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) +@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.CLASS) +annotation class JavaHappyAPI + +/** + * [Bot] 中为了让 Java 使用者调用更方便的 API 列表. + */ +@MiraiInternalAPI +@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "unused") +expect abstract class BotJavaHappyAPI() { // 不要使用 interface, 会无法添加默认实现 +} + +// 保留多平台结构, 以避免在 Android 和 JVM 都定义这个类 ---- 这会造成代码重复. +// 待 https://youtrack.jetbrains.com/issue/KT-27801 实现后修改为 hierarchical MPP 架构 + +// 待 https://youtrack.jetbrains.com/issue/KT-36740 修复后添加 Future 相关 API 到 hierarchical MPP 架构中 \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt new file mode 100644 index 000000000..6ea5e0857 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/lowLevelApi.kt @@ -0,0 +1,114 @@ +/* + * 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 + +import kotlinx.coroutines.Job +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.data.FriendInfo +import net.mamoe.mirai.data.GroupInfo +import net.mamoe.mirai.data.MemberInfo +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.WeakRef + +/** + * 标示这个 API 是低级的 API. + * + * 使用低级的 API 无法带来任何安全和便捷保障. + * 仅在某些使用结构化 API 可能影响性能的情况下使用这些低级 API. + */ +@RequiresOptIn +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY) +annotation class LowLevelAPI + +/** + * [Bot] 相关协议层低级 API. + */ +@MiraiExperimentalAPI +@Suppress("FunctionName", "unused") +@LowLevelAPI +interface LowLevelBotAPIAccessor { + /** + * 账号信息 + */ + @Deprecated("将来会做修改", level = DeprecationLevel.ERROR) + @MiraiExperimentalAPI + @LowLevelAPI + @MiraiInternalAPI + abstract val account: BotAccount + + /** + * 构造一个 [_lowLevelNewQQ] 对象. 它持有对 [Bot] 的弱引用([WeakRef]). + * + * [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job. + * 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭. + */ + @LowLevelAPI + fun _lowLevelNewQQ(friendInfo: FriendInfo): QQ + + /** + * 向服务器查询群列表. 返回值高 32 bits 为 uin, 低 32 bits 为 groupCode + */ + @LowLevelAPI + suspend fun _lowLevelQueryGroupList(): Sequence + + /** + * 向服务器查询群资料. 获得的仅为当前时刻的资料. + * 请优先使用 [Bot.getGroup] 然后查看群资料. + */ + @LowLevelAPI + suspend fun _lowLevelQueryGroupInfo(groupCode: Long): GroupInfo + + /** + * 向服务器查询群成员列表. + * 请优先使用 [Bot.getGroup], [Group.members] 查看群成员. + * + * 这个函数很慢. 请不要频繁使用. + * + * @see Group.calculateGroupUinByGroupCode 使用 groupCode 计算 groupUin + */ + @LowLevelAPI + suspend fun _lowLevelQueryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Sequence + + /** + * 撤回一条由机器人发送给好友的消息 + * @param messageId [MessageSource.id] + */ + @MiraiExperimentalAPI("还未实现") + @LowLevelAPI + suspend fun _lowLevelRecallFriendMessage(friendId: Long, messageId: Long, time: Long) + + /** + * 撤回一条群里的消息. 可以是机器人发送也可以是其他群员发送. + * @param messageId [MessageSource.id] + */ + @LowLevelAPI + suspend fun _lowLevelRecallGroupMessage(groupId: Long, messageId: Long) +} + +/** + * 撤回一条群里的消息. 可以是机器人发送也可以是其他群员发送. + */ +@Suppress("FunctionName") +@MiraiExperimentalAPI +@LowLevelAPI +suspend fun LowLevelBotAPIAccessor._lowLevelRecallGroupMessage( + groupId: Long, + messageSequenceId: Int, + messageRandom: Int +) { + this._lowLevelRecallGroupMessage( + groupId, + messageSequenceId.toLong().shl(32) or messageRandom.toLong().and(0xFFFFFFFFL) + ) +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt index 14c42998e..0f71cab78 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/FriendMessage.kt @@ -13,13 +13,15 @@ import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.message.data.MessageChain -import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.getValue +import net.mamoe.mirai.utils.unsafeWeakRef class FriendMessage( - bot: Bot, - override val sender: QQ, + sender: QQ, override val message: MessageChain -) : MessagePacket(bot), BroadcastControllable { +) : MessagePacket(), BroadcastControllable { + override val sender: QQ by sender.unsafeWeakRef() + override val bot: Bot get() = sender.bot override val subject: QQ get() = sender override fun toString(): String = "FriendMessage(sender=${sender.id}, message=$message)" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt index 0232fcecf..5c44dbbc9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/GroupMessage.kt @@ -14,17 +14,12 @@ import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.Event -import net.mamoe.mirai.message.data.At -import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.utils.getValue import net.mamoe.mirai.utils.unsafeWeakRef -import kotlin.jvm.JvmName @Suppress("unused", "NOTHING_TO_INLINE") class GroupMessage( - bot: Bot, - group: Group, val senderName: String, /** * 发送方权限. @@ -32,36 +27,15 @@ class GroupMessage( val permission: MemberPermission, sender: Member, override val message: MessageChain -) : MessagePacket(bot), Event { - val group: Group by group.unsafeWeakRef() +) : MessagePacket(), Event { override val sender: Member by sender.unsafeWeakRef() + val group: Group get() = sender.group + override val bot: Bot get() = sender.bot override val subject: Group get() = group - inline fun At.member(): Member = group[this.target] inline fun Long.member(): Member = group[this] - - /** - * 给这个消息事件的主体发送引用回复消息 - * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 - * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 - */ - suspend inline fun quoteReply(message: MessageChain) = reply(this.message.quote() + message) - - suspend inline fun quoteReply(message: Message) = reply(this.message.quote() + message) - suspend inline fun quoteReply(plain: String) = reply(this.message.quote() + plain) - - - @JvmName("reply2") - suspend inline fun String.quoteReply() = quoteReply(this) - - @JvmName("reply2") - suspend inline fun Message.quoteReply() = quoteReply(this) - - @JvmName("reply2") - suspend inline fun MessageChain.quoteReply() = quoteReply(this) - override fun toString(): String = "GroupMessage(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt index 4aeb9c04c..951d4360c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessagePacket.kt @@ -11,37 +11,42 @@ package net.mamoe.mirai.message -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.IoBuffer -import kotlinx.io.core.readBytes +import kotlinx.coroutines.io.ByteReadChannel import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.data.EventPacket +import net.mamoe.mirai.data.Packet import net.mamoe.mirai.event.events.BotEvent +import net.mamoe.mirai.event.subscribingGet +import net.mamoe.mirai.event.subscribingGetOrNull import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.recall +import net.mamoe.mirai.recallIn import net.mamoe.mirai.utils.* +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmName /** * 一条从服务器接收到的消息事件. * 请查看各平台的 `actual` 实现的说明. */ -@UseExperimental(MiraiInternalAPI::class) -expect abstract class MessagePacket(bot: Bot) : MessagePacketBase +@OptIn(MiraiInternalAPI::class) +expect abstract class MessagePacket() : MessagePacketBase /** * 仅内部使用, 请使用 [MessagePacket] */ // Tips: 在 IntelliJ 中 (左侧边栏) 打开 `Structure`, 可查看类结构 -@Suppress("NOTHING_TO_INLINE") +@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") @MiraiInternalAPI -abstract class MessagePacketBase(_bot: Bot) : EventPacket, BotEvent { +abstract class MessagePacketBase : Packet, BotEvent { /** * 接受到这条消息的 */ - override val bot: Bot by _bot.unsafeWeakRef() + @WeakRefProperty + abstract override val bot: Bot /** * 消息事件主体. @@ -51,6 +56,7 @@ abstract class MessagePacketBase(_bot: Bot) : * * 在回复消息时, 可通过 [subject] 作为回复对象 */ + @WeakRefProperty abstract val subject: TSubject /** @@ -58,6 +64,7 @@ abstract class MessagePacketBase(_bot: Bot) : * * 在好友消息时为 [QQ] 的实例, 在群消息时为 [Member] 的实例 */ + @WeakRefProperty abstract val sender: TSender /** @@ -73,29 +80,43 @@ abstract class MessagePacketBase(_bot: Bot) : * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 */ - suspend inline fun reply(message: MessageChain) = subject.sendMessage(message) + suspend inline fun reply(message: MessageChain): MessageReceipt = + subject.sendMessage(message) as MessageReceipt - suspend inline fun reply(message: Message) = subject.sendMessage(message.toChain()) - suspend inline fun reply(plain: String) = subject.sendMessage(plain.singleChain()) + suspend inline fun reply(message: Message): MessageReceipt = + subject.sendMessage(message.asMessageChain()) as MessageReceipt + + suspend inline fun reply(plain: String): MessageReceipt = + subject.sendMessage(plain.toMessage().asMessageChain()) as MessageReceipt @JvmName("reply1") - suspend inline fun String.reply() = reply(this) + suspend inline fun String.reply(): MessageReceipt = reply(this) @JvmName("reply1") - suspend inline fun Message.reply() = reply(this) + suspend inline fun Message.reply(): MessageReceipt = reply(this) @JvmName("reply1") - suspend inline fun MessageChain.reply() = reply(this) - + suspend inline fun MessageChain.reply(): MessageReceipt = reply(this) // endregion - // region + // region 撤回 + suspend inline fun MessageChain.recall() = bot.recall(this) + suspend inline fun MessageSource.recall() = bot.recall(this) + suspend inline fun QuoteReply.recall() = bot.recall(this.source) + inline fun MessageChain.recallIn( + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext + ) = bot.recallIn(this, millis, coroutineContext) - /** - * 引用这个消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException] - */ - inline fun MessageChain.quote(): MessageChain = this.quote(sender as? Member ?: error("only group message can be quoted")) + inline fun MessageSource.recallIn( + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext + ) = bot.recallIn(this, millis, coroutineContext) + inline fun QuoteReply.recallIn( + millis: Long, + coroutineContext: CoroutineContext = EmptyCoroutineContext + ) = bot.recallIn(this.source, millis, coroutineContext) // endregion // region 上传图片 @@ -103,46 +124,185 @@ abstract class MessagePacketBase(_bot: Bot) : // endregion // region 发送图片 - suspend inline fun ExternalImage.send() = this.sendTo(subject) + suspend inline fun ExternalImage.send(): MessageReceipt = this.sendTo(subject) - suspend inline fun Image.send() = this.sendTo(subject) - suspend inline fun Message.send() = this.sendTo(subject) - suspend inline fun String.send() = this.toMessage().sendTo(subject) + suspend inline fun Image.send(): MessageReceipt = this.sendTo(subject) + suspend inline fun Message.send(): MessageReceipt = this.sendTo(subject) + suspend inline fun String.send(): MessageReceipt = this.toMessage().sendTo(subject) // endregion + + /** + * 给这个消息事件的主体发送引用回复消息 + * 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息 + * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 + */ + suspend inline fun quoteReply(message: MessageChain): MessageReceipt = + reply(this.message.quote() + message) + + suspend inline fun quoteReply(message: Message): MessageReceipt = reply(this.message.quote() + message) + suspend inline fun quoteReply(plain: String): MessageReceipt = reply(this.message.quote() + plain) + + @JvmName("reply2") + suspend inline fun String.quoteReply(): MessageReceipt = quoteReply(this) + + @JvmName("reply2") + suspend inline fun Message.quoteReply(): MessageReceipt = quoteReply(this) + + @JvmName("reply2") + suspend inline fun MessageChain.quoteReply(): MessageReceipt = quoteReply(this) + + /** + * 引用这个消息 + */ + inline fun MessageChain.quote(): QuoteReplyToSend = this.quote(sender) + + operator fun get(at: Message.Key): M { + return this.message[at] + } + /** * 创建 @ 这个账号的消息. 当且仅当消息为群消息时可用. 否则将会抛出 [IllegalArgumentException] */ - inline fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage")) + fun QQ.at(): At = At(this as? Member ?: error("`QQ.at` can only be used in GroupMessage")) + + fun At.member(): Member = (this@MessagePacketBase as? GroupMessage)?.group?.get(this.target) + ?: error("`At.member` can only be used in GroupMessage") // endregion // region 下载图片 + + /** - * 将图片下载到内存. + * 获取图片下载链接 * - * 非常不推荐这样做. + * @return "http://gchat.qpic.cn/gchatpic_new/..." */ - @Deprecated("内存使用效率十分低下", ReplaceWith("this.download()"), DeprecationLevel.WARNING) - suspend inline fun Image.downloadAsByteArray(): ByteArray = bot.run { download().readBytes() } - - // TODO: 2020/2/5 为下载图片添加文件系统的存储方式 + suspend inline fun Image.url(): String = bot.queryImageUrl(this@url) /** - * 将图片下载到内存缓存中 (使用 [IoBuffer.Pool]) + * 获取图片下载链接并开始下载. + * + * @see ByteReadChannel.copyAndClose + * @see ByteReadChannel.copyTo */ - suspend inline fun Image.download(): ByteReadPacket = bot.run { download() } + suspend inline fun Image.channel(): ByteReadChannel = bot.openChannel(this) // endregion +} - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.target)")) - fun At.qq(): QQ = bot.getFriend(this.target) +/** + * 判断两个 [MessagePacket] 的 [MessagePacket.sender] 和 [MessagePacket.subject] 是否相同 + */ +fun MessagePacket<*, *>.isContextIdenticalWith(another: MessagePacket<*, *>): Boolean { + return this.sender == another.sender && this.subject == another.subject +} - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this.toLong())")) - fun Int.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0).toLong()) +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 + * + * @see subscribingGet + */ +suspend inline fun > P.nextMessage( + timeoutMillis: Long = -1, + crossinline filter: P.(P) -> Boolean +): MessageChain { + return subscribingGet(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessage) }?.takeIf { filter(it, it) } + }.message +} - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getFriend(this)")) - fun Long.qq(): QQ = bot.getFriend(this.coerceAtLeastOrFail(0)) +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @param filter 过滤器. 返回非 null 则代表得到了需要的值. [subscribingGet] 会返回这个值 + * @return 消息链. 超时时返回 `null` + * + * @see subscribingGetOrNull + */ +suspend inline fun > P.nextMessageOrNull( + timeoutMillis: Long = -1, + crossinline filter: P.(P) -> Boolean +): MessageChain? { + return subscribingGetOrNull(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) }?.takeIf { filter(it, it) } + }?.message +} - @Deprecated(message = "这个函数有歧义, 将在不久后删除", replaceWith = ReplaceWith("bot.getGroup(this)")) - fun Long.group(): Group = bot.getGroup(this) +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * + * @see subscribingGet + */ +suspend inline fun > P.nextMessage( + timeoutMillis: Long = -1 +): MessageChain { + return subscribingGet(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessage) } + }.message +} + +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @return 消息链. 超时时返回 `null` + * + * @see subscribingGetOrNull + */ +suspend inline fun > P.nextMessageOrNull( + timeoutMillis: Long = -1 +): MessageChain? { + return subscribingGetOrNull(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageOrNull) } + }?.message +} + +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * + * @see subscribingGet + */ +suspend inline fun MessagePacket<*, *>.nextMessageContaining( + timeoutMillis: Long = -1 +): M { + return subscribingGet, MessagePacket<*, *>>(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageContaining) } + }.message.first() +} + +/** + * 挂起当前协程, 等待下一条 [MessagePacket.sender] 和 [MessagePacket.subject] 与 [this] 相同并含有 [M] 类型的消息的 [MessagePacket] + * + * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. + * + * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 + * @return 指定类型的消息. 超时时返回 `null` + * + * @see subscribingGetOrNull + */ +suspend inline fun MessagePacket<*, *>.nextMessageContainingOrNull( + timeoutMillis: Long = -1 +): M? { + return subscribingGetOrNull, MessagePacket<*, *>>(timeoutMillis) { + takeIf { this.isContextIdenticalWith(this@nextMessageContainingOrNull) } + }?.message?.first() } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt new file mode 100644 index 000000000..a3469caae --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/MessageReceipt.kt @@ -0,0 +1,116 @@ +/* + * 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.message + +import kotlinx.coroutines.Job +import net.mamoe.mirai.Bot +import net.mamoe.mirai.LowLevelAPI +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.recallIn + +/** + * 发送消息后得到的回执. 可用于撤回. + * + * 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问. + * + * @see Group.sendMessage 发送群消息, 返回回执(此对象) + * @see QQ.sendMessage 发送群消息, 返回回执(此对象) + * + * @see MessageReceipt.sourceId 源 id + * @see MessageReceipt.sourceSequenceId 源序列号 + * @see MessageReceipt.sourceTime 源时间 + */ +expect open class MessageReceipt( + source: MessageSource, + target: C, + botAsMember: Member? +) { + val source: MessageSource + + /** + * 发送目标, 为 [Group] 或 [QQ] + */ + val target: C + + /** + * 是否为发送给群的消息的回执 + */ + val isToGroup: Boolean + + /** + * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. + * + * @see Bot.recall + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + suspend fun recall() + + /** + * 在一段时间后撤回这条消息.. [recall] 或 [recallIn] 只能被调用一次. + * + * @param millis 延迟时间, 单位为毫秒 + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + fun recallIn(millis: Long): Job + + /** + * [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息. + * @see MessageChain.quote 引用一条消息 + */ + open suspend fun quote(): QuoteReplyToSend + + /** + * 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable]. + * 在 sequenceId 可用前就发送这条消息则会导致一个异常. + * 当且仅当用于存储而不用于发送时使用这个方法. + * + * @see MessageChain.quote 引用一条消息 + */ + @LowLevelAPI + @Suppress("FunctionName") + fun _unsafeQuote(): QuoteReplyToSend + + /** + * 引用这条消息并回复. + * @see MessageChain.quote 引用一条消息 + */ + suspend fun quoteReply(message: MessageChain) +} + +/** + * 获取源消息 [MessageSource.id] + * + * @see MessageSource.id + */ +inline val MessageReceipt<*>.sourceId: Long get() = this.source.id + +/** + * 获取源消息 [MessageSource.sequenceId] + * + * @see MessageSource.sequenceId + */ +inline val MessageReceipt<*>.sourceSequenceId: Int get() = this.source.sequenceId + +/** + * 获取源消息 [MessageSource.time] + * + * @see MessageSource.time + */ +inline val MessageReceipt<*>.sourceTime: Long get() = this.source.time + +suspend inline fun MessageReceipt.quoteReply(message: Message) { + return this.quoteReply(message.asMessageChain()) +} + +suspend inline fun MessageReceipt.quoteReply(message: String) { + return this.quoteReply(message.toMessage().asMessageChain()) +} + diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt index dc2b7bc78..c9204ab0d 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/At.kt @@ -7,12 +7,19 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + @file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.message.data +import net.mamoe.mirai.LowLevelAPI import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.contact.nameCardOrNick +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.jvm.JvmStatic /** @@ -20,25 +27,33 @@ import net.mamoe.mirai.utils.MiraiInternalAPI * * @see AtAll 全体成员 */ -class At @MiraiInternalAPI constructor(val target: Long, val display: String) : Message { - @UseExperimental(MiraiInternalAPI::class) - constructor(member: Member) : this(member.id, "@${member.nick}") +class At +private constructor(val target: Long, val display: String) : Message, MessageContent { + + /** + * 构造一个 [At] 实例. 这是唯一的公开的构造方式. + */ + constructor(member: Member) : this(member.id, "@${member.nameCardOrNick}") override fun toString(): String = display - companion object Key : Message.Key - - override fun eq(other: Message): Boolean { - return other is At && other.target == this.target + companion object Key : Message.Key { + /** + * 构造一个 [At], 仅供内部使用, 否则可能造成消息无法发出的问题. + */ + @Suppress("FunctionName") + @JvmStatic + @LowLevelAPI + fun _lowLevelConstructAtInstance(target: Long, display: String): At = At(target, display) } // 自动为消息补充 " " - override fun followedBy(tail: Message): MessageChain { - if(tail is PlainText && tail.stringValue.startsWith(' ')){ - return super.followedBy(tail) + override fun followedBy(tail: Message): CombinedMessage { + if (tail is PlainText && tail.stringValue.startsWith(' ')) { + return super.followedBy(tail) } - return super.followedBy(PlainText(" ")) + tail + return super.followedBy(PlainText(" ")) + tail } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt index 69bb6c392..df748ddd5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/AtAll.kt @@ -7,22 +7,30 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + package net.mamoe.mirai.message.data +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + /** - * "@全体成员" + * "@全体成员". + * + * 非会员每天只能发送 10 次 [AtAll]. 超出部分会被以普通文字看待. * * @see At at 单个群成员 */ -object AtAll : Message, Message.Key { +object AtAll : Message, Message.Key, MessageContent { override fun toString(): String = "@全体成员" // 自动为消息补充 " " - override fun followedBy(tail: Message): MessageChain { - if(tail is PlainText && tail.stringValue.startsWith(' ')){ - return super.followedBy(tail) + override fun followedBy(tail: Message): CombinedMessage { + if (tail is PlainText && tail.stringValue.startsWith(' ')) { + return super.followedBy(tail) } - return super.followedBy(PlainText(" ")) + tail + return super.followedBy(PlainText(" ")) + tail } } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt new file mode 100644 index 000000000..73c4fa2c8 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/CombinedMessage.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + +package net.mamoe.mirai.message.data + +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + +/** + * 链接的两个消息. + * + * @see Message.plus + * + * Left-biased list + */ +class CombinedMessage( + val left: Message, + val element: Message +) : Iterable, Message { + + // 不要把它用作 local function, 会编译错误 + private suspend fun SequenceScope.yieldCombinedOrElements(message: Message) { + when (message) { + is CombinedMessage -> { + // fast path, 避免创建新的 iterator, 也不会挂起协程 + yieldCombinedOrElements(message.element) + yieldCombinedOrElements(message.left) + } + is MessageChain -> { + // 更好的性能, 因为协程不会挂起. + // 这可能会导致爆栈 (十万个元素), 但作为消息序列足够了. + message.forEach { yieldCombinedOrElements(it) } + } + else -> { + check(message is SingleMessage) { "unsupported Message type. DO NOT CREATE YOUR OWN Message TYPE!" } + yield(message) + } + } + } + + fun asSequence(): Sequence = sequence { + yieldCombinedOrElements(this@CombinedMessage) + } + + override fun iterator(): Iterator { + return asSequence().iterator() + } + + override fun toString(): String { + return element.toString() + left.toString() + } + + fun isFlat(): Boolean { + return element is SingleMessage && left is SingleMessage + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt index fa9c0c70b..88da0932c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Face.kt @@ -7,311 +7,164 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + package net.mamoe.mirai.message.data -import kotlin.jvm.JvmStatic +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName /** * QQ 自带表情 */ -inline class Face(val id: FaceId) : Message { - override fun toString(): String = "[face${id.value}]" +class Face(val id: Int) : Message, MessageContent { + override fun toString(): String = "[mirai:face$id]" - companion object Key : Message.Key - - override fun eq(other: Message): Boolean { - return other is Face && other.id == this.id + /** + * @author LamGC + */ + @Suppress("SpellCheckingInspection", "unused") + companion object IdList : Message.Key { + const val unknown: Int = 0xff + const val jingya: Int = 0 + const val piezui: Int = 1 + const val se: Int = 2 + const val fadai: Int = 3 + const val deyi: Int = 4 + const val liulei: Int = 5 + const val haixiu: Int = 6 + const val bizui: Int = 7 + const val shui: Int = 8 + const val daku: Int = 9 + const val ganga: Int = 10 + const val fanu: Int = 11 + const val tiaopi: Int = 12 + const val ciya: Int = 13 + const val weixiao: Int = 14 + const val nanguo: Int = 15 + const val ku: Int = 16 + const val zhuakuang: Int = 18 + const val tu: Int = 19 + const val touxiao: Int = 20 + const val keai: Int = 21 + const val baiyan: Int = 22 + const val aoman: Int = 23 + const val ji_e: Int = 24 + const val kun: Int = 25 + const val jingkong: Int = 26 + const val liuhan: Int = 27 + const val hanxiao: Int = 28 + const val dabing: Int = 29 + const val fendou: Int = 30 + const val zhouma: Int = 31 + const val yiwen: Int = 32 + const val yun: Int = 34 + const val zhemo: Int = 35 + const val shuai: Int = 36 + const val kulou: Int = 37 + const val qiaoda: Int = 38 + const val zaijian: Int = 39 + const val fadou: Int = 41 + const val aiqing: Int = 42 + const val tiaotiao: Int = 43 + const val zhutou: Int = 46 + const val yongbao: Int = 49 + const val dan_gao: Int = 53 + const val shandian: Int = 54 + const val zhadan: Int = 55 + const val dao: Int = 56 + const val zuqiu: Int = 57 + const val bianbian: Int = 59 + const val kafei: Int = 60 + const val fan: Int = 61 + const val meigui: Int = 63 + const val diaoxie: Int = 64 + const val aixin: Int = 66 + const val xinsui: Int = 67 + const val liwu: Int = 69 + const val taiyang: Int = 74 + const val yueliang: Int = 75 + const val qiang: Int = 76 + const val ruo: Int = 77 + const val woshou: Int = 78 + const val shengli: Int = 79 + const val feiwen: Int = 85 + const val naohuo: Int = 86 + const val xigua: Int = 89 + const val lenghan: Int = 96 + const val cahan: Int = 97 + const val koubi: Int = 98 + const val guzhang: Int = 99 + const val qiudale: Int = 100 + const val huaixiao: Int = 101 + const val zuohengheng: Int = 102 + const val youhengheng: Int = 103 + const val haqian: Int = 104 + const val bishi: Int = 105 + const val weiqu: Int = 106 + const val kuaikule: Int = 107 + const val yinxian: Int = 108 + const val qinqin: Int = 109 + const val xia: Int = 110 + const val kelian: Int = 111 + const val caidao: Int = 112 + const val pijiu: Int = 113 + const val lanqiu: Int = 114 + const val pingpang: Int = 115 + const val shiai: Int = 116 + const val piaochong: Int = 117 + const val baoquan: Int = 118 + const val gouyin: Int = 119 + const val quantou: Int = 120 + const val chajin: Int = 121 + const val aini: Int = 122 + const val bu: Int = 123 + const val hao: Int = 124 + const val zhuanquan: Int = 125 + const val ketou: Int = 126 + const val huitou: Int = 127 + const val tiaosheng: Int = 128 + const val huishou: Int = 129 + const val jidong: Int = 130 + const val jiewu: Int = 131 + const val xianwen: Int = 132 + const val zuotaiji: Int = 133 + const val youtaiji: Int = 134 + const val shuangxi: Int = 136 + const val bianpao: Int = 137 + const val denglong: Int = 138 + const val facai: Int = 139 + const val K_ge: Int = 140 + const val gouwu: Int = 141 + const val youjian: Int = 142 + const val shuai_qi: Int = 143 + const val hecai: Int = 144 + const val qidao: Int = 145 + const val baojin: Int = 146 + const val bangbangtang: Int = 147 + const val he_nai: Int = 148 + const val xiamian: Int = 149 + const val xiangjiao: Int = 150 + const val feiji: Int = 151 + const val kaiche: Int = 152 + const val gaotiezuochetou: Int = 153 + const val chexiang: Int = 154 + const val gaotieyouchetou: Int = 155 + const val duoyun: Int = 156 + const val xiayu: Int = 157 + const val chaopiao: Int = 158 + const val xiongmao: Int = 159 + const val dengpao: Int = 160 + const val fengche: Int = 161 + const val naozhong: Int = 162 + const val dasan: Int = 163 + const val caiqiu: Int = 164 + const val zuanjie: Int = 165 + const val shafa: Int = 166 + const val zhijin: Int = 167 + const val yao: Int = 168 + const val shouqiang: Int = 169 + const val qingwa: Int = 170 } -} - -/** - * @author LamGC - */ -@Suppress("SpellCheckingInspection", "unused") -@UseExperimental(ExperimentalUnsignedTypes::class) -inline class FaceId constructor(inline val value: UByte) { - companion object { - @JvmStatic - val unknown: FaceId = FaceId(0xffu) - @JvmStatic - val jingya: FaceId = FaceId(0u) - @JvmStatic - val piezui: FaceId = FaceId(1u) - @JvmStatic - val se: FaceId = FaceId(2u) - @JvmStatic - val fadai: FaceId = FaceId(3u) - @JvmStatic - val deyi: FaceId = FaceId(4u) - @JvmStatic - val liulei: FaceId = FaceId(5u) - @JvmStatic - val haixiu: FaceId = FaceId(6u) - @JvmStatic - val bizui: FaceId = FaceId(7u) - @JvmStatic - val shui: FaceId = FaceId(8u) - @JvmStatic - val daku: FaceId = FaceId(9u) - @JvmStatic - val ganga: FaceId = FaceId(10u) - @JvmStatic - val fanu: FaceId = FaceId(11u) - @JvmStatic - val tiaopi: FaceId = FaceId(12u) - @JvmStatic - val ciya: FaceId = FaceId(13u) - @JvmStatic - val weixiao: FaceId = FaceId(14u) - @JvmStatic - val nanguo: FaceId = FaceId(15u) - @JvmStatic - val ku: FaceId = FaceId(16u) - @JvmStatic - val zhuakuang: FaceId = FaceId(18u) - @JvmStatic - val tu: FaceId = FaceId(19u) - @JvmStatic - val touxiao: FaceId = FaceId(20u) - @JvmStatic - val keai: FaceId = FaceId(21u) - @JvmStatic - val baiyan: FaceId = FaceId(22u) - @JvmStatic - val aoman: FaceId = FaceId(23u) - @JvmStatic - val ji_e: FaceId = FaceId(24u) - @JvmStatic - val kun: FaceId = FaceId(25u) - @JvmStatic - val jingkong: FaceId = FaceId(26u) - @JvmStatic - val liuhan: FaceId = FaceId(27u) - @JvmStatic - val hanxiao: FaceId = FaceId(28u) - @JvmStatic - val dabing: FaceId = FaceId(29u) - @JvmStatic - val fendou: FaceId = FaceId(30u) - @JvmStatic - val zhouma: FaceId = FaceId(31u) - @JvmStatic - val yiwen: FaceId = FaceId(32u) - @JvmStatic - val yun: FaceId = FaceId(34u) - @JvmStatic - val zhemo: FaceId = FaceId(35u) - @JvmStatic - val shuai: FaceId = FaceId(36u) - @JvmStatic - val kulou: FaceId = FaceId(37u) - @JvmStatic - val qiaoda: FaceId = FaceId(38u) - @JvmStatic - val zaijian: FaceId = FaceId(39u) - @JvmStatic - val fadou: FaceId = FaceId(41u) - @JvmStatic - val aiqing: FaceId = FaceId(42u) - @JvmStatic - val tiaotiao: FaceId = FaceId(43u) - @JvmStatic - val zhutou: FaceId = FaceId(46u) - @JvmStatic - val yongbao: FaceId = FaceId(49u) - @JvmStatic - val dan_gao: FaceId = FaceId(53u) - @JvmStatic - val shandian: FaceId = FaceId(54u) - @JvmStatic - val zhadan: FaceId = FaceId(55u) - @JvmStatic - val dao: FaceId = FaceId(56u) - @JvmStatic - val zuqiu: FaceId = FaceId(57u) - @JvmStatic - val bianbian: FaceId = FaceId(59u) - @JvmStatic - val kafei: FaceId = FaceId(60u) - @JvmStatic - val fan: FaceId = FaceId(61u) - @JvmStatic - val meigui: FaceId = FaceId(63u) - @JvmStatic - val diaoxie: FaceId = FaceId(64u) - @JvmStatic - val aixin: FaceId = FaceId(66u) - @JvmStatic - val xinsui: FaceId = FaceId(67u) - @JvmStatic - val liwu: FaceId = FaceId(69u) - @JvmStatic - val taiyang: FaceId = FaceId(74u) - @JvmStatic - val yueliang: FaceId = FaceId(75u) - @JvmStatic - val qiang: FaceId = FaceId(76u) - @JvmStatic - val ruo: FaceId = FaceId(77u) - @JvmStatic - val woshou: FaceId = FaceId(78u) - @JvmStatic - val shengli: FaceId = FaceId(79u) - @JvmStatic - val feiwen: FaceId = FaceId(85u) - @JvmStatic - val naohuo: FaceId = FaceId(86u) - @JvmStatic - val xigua: FaceId = FaceId(89u) - @JvmStatic - val lenghan: FaceId = FaceId(96u) - @JvmStatic - val cahan: FaceId = FaceId(97u) - @JvmStatic - val koubi: FaceId = FaceId(98u) - @JvmStatic - val guzhang: FaceId = FaceId(99u) - @JvmStatic - val qiudale: FaceId = FaceId(100u) - @JvmStatic - val huaixiao: FaceId = FaceId(101u) - @JvmStatic - val zuohengheng: FaceId = FaceId(102u) - @JvmStatic - val youhengheng: FaceId = FaceId(103u) - @JvmStatic - val haqian: FaceId = FaceId(104u) - @JvmStatic - val bishi: FaceId = FaceId(105u) - @JvmStatic - val weiqu: FaceId = FaceId(106u) - @JvmStatic - val kuaikule: FaceId = FaceId(107u) - @JvmStatic - val yinxian: FaceId = FaceId(108u) - @JvmStatic - val qinqin: FaceId = FaceId(109u) - @JvmStatic - val xia: FaceId = FaceId(110u) - @JvmStatic - val kelian: FaceId = FaceId(111u) - @JvmStatic - val caidao: FaceId = FaceId(112u) - @JvmStatic - val pijiu: FaceId = FaceId(113u) - @JvmStatic - val lanqiu: FaceId = FaceId(114u) - @JvmStatic - val pingpang: FaceId = FaceId(115u) - @JvmStatic - val shiai: FaceId = FaceId(116u) - @JvmStatic - val piaochong: FaceId = FaceId(117u) - @JvmStatic - val baoquan: FaceId = FaceId(118u) - @JvmStatic - val gouyin: FaceId = FaceId(119u) - @JvmStatic - val quantou: FaceId = FaceId(120u) - @JvmStatic - val chajin: FaceId = FaceId(121u) - @JvmStatic - val aini: FaceId = FaceId(122u) - @JvmStatic - val bu: FaceId = FaceId(123u) - @JvmStatic - val hao: FaceId = FaceId(124u) - @JvmStatic - val zhuanquan: FaceId = FaceId(125u) - @JvmStatic - val ketou: FaceId = FaceId(126u) - @JvmStatic - val huitou: FaceId = FaceId(127u) - @JvmStatic - val tiaosheng: FaceId = FaceId(128u) - @JvmStatic - val huishou: FaceId = FaceId(129u) - @JvmStatic - val jidong: FaceId = FaceId(130u) - @JvmStatic - val jiewu: FaceId = FaceId(131u) - @JvmStatic - val xianwen: FaceId = FaceId(132u) - @JvmStatic - val zuotaiji: FaceId = FaceId(133u) - @JvmStatic - val youtaiji: FaceId = FaceId(134u) - @JvmStatic - val shuangxi: FaceId = FaceId(136u) - @JvmStatic - val bianpao: FaceId = FaceId(137u) - @JvmStatic - val denglong: FaceId = FaceId(138u) - @JvmStatic - val facai: FaceId = FaceId(139u) - @JvmStatic - val K_ge: FaceId = FaceId(140u) - @JvmStatic - val gouwu: FaceId = FaceId(141u) - @JvmStatic - val youjian: FaceId = FaceId(142u) - @JvmStatic - val shuai_qi: FaceId = FaceId(143u) - @JvmStatic - val hecai: FaceId = FaceId(144u) - @JvmStatic - val qidao: FaceId = FaceId(145u) - @JvmStatic - val baojin: FaceId = FaceId(146u) - @JvmStatic - val bangbangtang: FaceId = FaceId(147u) - @JvmStatic - val he_nai: FaceId = FaceId(148u) - @JvmStatic - val xiamian: FaceId = FaceId(149u) - @JvmStatic - val xiangjiao: FaceId = FaceId(150u) - @JvmStatic - val feiji: FaceId = FaceId(151u) - @JvmStatic - val kaiche: FaceId = FaceId(152u) - @JvmStatic - val gaotiezuochetou: FaceId = FaceId(153u) - @JvmStatic - val chexiang: FaceId = FaceId(154u) - @JvmStatic - val gaotieyouchetou: FaceId = FaceId(155u) - @JvmStatic - val duoyun: FaceId = FaceId(156u) - @JvmStatic - val xiayu: FaceId = FaceId(157u) - @JvmStatic - val chaopiao: FaceId = FaceId(158u) - @JvmStatic - val xiongmao: FaceId = FaceId(159u) - @JvmStatic - val dengpao: FaceId = FaceId(160u) - @JvmStatic - val fengche: FaceId = FaceId(161u) - @JvmStatic - val naozhong: FaceId = FaceId(162u) - @JvmStatic - val dasan: FaceId = FaceId(163u) - @JvmStatic - val caiqiu: FaceId = FaceId(164u) - @JvmStatic - val zuanjie: FaceId = FaceId(165u) - @JvmStatic - val shafa: FaceId = FaceId(166u) - @JvmStatic - val zhijin: FaceId = FaceId(167u) - @JvmStatic - val yao: FaceId = FaceId(168u) - @JvmStatic - val shouqiang: FaceId = FaceId(169u) - @JvmStatic - val qingwa: FaceId = FaceId(170u) - } - - override fun toString(): String = "$FaceId($value)" -} +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt index 738786749..dc78d752c 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Image.kt @@ -7,27 +7,30 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + @file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.message.data import kotlinx.serialization.Serializable import kotlinx.serialization.Transient +import net.mamoe.mirai.Bot +import net.mamoe.mirai.BotImpl +import net.mamoe.mirai.contact.Contact +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.chunkedHexToBytes +import kotlin.js.JsName +import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 自定义表情 (收藏的表情), 图片 */ -sealed class Image : Message { - companion object Key : Message.Key { - @JvmName("fromId") - operator fun invoke(imageId: String): Image = when (imageId.length) { - 37 -> NotOnlineImageFromFile(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f - 42 -> CustomFaceFromFile(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png - else -> throw IllegalArgumentException("Bad imageId, expecting length=37 or 42, got ${imageId.length}") - } - } +interface Image : Message, MessageContent { + companion object Key : Message.Key /** * 图片的 id. 只需要有这个 id 即可发送图片. @@ -36,19 +39,84 @@ sealed class Image : Message { * 好友图片的 id: `/f8f1ab55-bf8e-4236-b55e-955848d7069f` * 群图片的 id: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png` */ - abstract val imageId: String + val imageId: String +} +@Suppress("FunctionName") +@JsName("newImage") +@JvmName("newImage") +fun Image(imageId: String): Image = when (imageId.length) { + 37 -> OfflineFriendImage(imageId) // /f8f1ab55-bf8e-4236-b55e-955848d7069f + 42 -> OfflineGroupImage(imageId) // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png + else -> throw IllegalArgumentException("Bad imageId, expecting length=37 or 42, got ${imageId.length}") +} + +@MiraiInternalAPI("使用 Image") +abstract class AbstractImage internal constructor() : Image { final override fun toString(): String { - return "[image::$imageId]" - } - - final override fun eq(other: Message): Boolean { - return if (other is Image) return other.imageId == this.imageId - else this.toString() == other.toString() + return "[mirai:$imageId]" } } -abstract class CustomFace : Image() { +// region 在线图片 + +/** + * 在服务器上的图片. 它可以直接获取下载链接. + * + * 一般由 [Contact.uploadImage] 得到 + */ +interface OnlineImage : Image { + /** + * 原图下载链接. 包含域名 + */ + val originUrl: String +} + +/** + * 查询原图下载链接. + */ +suspend fun Image.queryUrl(): String { + @OptIn(MiraiInternalAPI::class) + return when (this) { + is OnlineImage -> this.originUrl + else -> BotImpl.instances.peekFirst().get()?.queryImageUrl(this) + ?: error("No Bot available to query image url") + } +} + +// endregion 在线图片 + + +// region 离线图片 + +/** + * 离线的图片, 即为客户端主动上传到服务器而获得的 [Image] 实例. + * 不能直接获取它在服务器上的链接. 需要通过 [Bot.queryImageUrl] 查询 + * + * 一般由 [Contact.uploadImage] 得到 + * @see queryOriginUrl + */ +interface OfflineImage : Image + +/** + * 原图下载链接. 包含域名 + */ +suspend fun OfflineImage.queryOriginUrl(): String { + @OptIn(MiraiInternalAPI::class) + return BotImpl.instances.peekFirst().get()?.queryImageUrl(this) ?: error("No Bot available to query image url") +} + +// endregion 离线图片 + +// region 群图片 + + +/** + * 群图片 + */ +// CustomFace +@OptIn(MiraiInternalAPI::class) +sealed class GroupImage : AbstractImage() { abstract val filepath: String abstract val fileId: Int abstract val serverIp: Int @@ -67,30 +135,14 @@ abstract class CustomFace : Image() { abstract val original: Int } -private val EMPTY_BYTE_ARRAY = ByteArray(0) - -private fun calculateImageMd5ByImageId(imageId: String): ByteArray { - return if (imageId.startsWith('/')) { - imageId - .drop(1) - .replace("-", "") - .take(16 * 2) - .chunkedHexToBytes() - } else { - imageId - .substringAfter('{') - .substringBefore('}') - .replace("-", "") - .take(16 * 2) - .chunkedHexToBytes() - } -} - +/** + * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl] + */ @Serializable -data class CustomFaceFromFile( +data class OfflineGroupImage( override val filepath: String, // {01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png override val md5: ByteArray -) : CustomFace() { +) : GroupImage(), OfflineImage { constructor(imageId: String) : this(filepath = imageId, md5 = calculateImageMd5ByImageId(imageId)) override val fileId: Int get() = 0 @@ -114,14 +166,26 @@ data class CustomFaceFromFile( } override fun equals(other: Any?): Boolean { - return other is CustomFaceFromFile && other.md5.contentEquals(this.md5) && other.filepath == this.filepath + return other is OfflineGroupImage && other.md5.contentEquals(this.md5) && other.filepath == this.filepath } } /** - * 电脑可能看不到这个消息. + * 接收消息时获取到的 [GroupImage]. 它可以直接获取下载链接 [originUrl] */ -abstract class NotOnlineImage : Image() { +abstract class OnlineGroupImage : GroupImage(), OnlineImage + + +// endregion 群图片 + +// region 好友图片 + + +/** + * 好友图片 + */ // NotOnlineImage +@OptIn(MiraiInternalAPI::class) +sealed class FriendImage : AbstractImage() { abstract val resourceId: String abstract val md5: ByteArray abstract val filepath: String @@ -137,7 +201,10 @@ abstract class NotOnlineImage : Image() { override val imageId: String get() = resourceId } -data class NotOnlineImageFromFile( +/** + * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [Bot.queryImageUrl] + */ +data class OfflineFriendImage( override val resourceId: String, override val md5: ByteArray, @Transient override val filepath: String = resourceId, @@ -148,7 +215,7 @@ data class NotOnlineImageFromFile( @Transient override val imageType: Int = 1000, @Transient override val downloadPath: String = resourceId, @Transient override val fileId: Int = 0 -) : NotOnlineImage() { +) : FriendImage(), OfflineImage { constructor(imageId: String) : this(resourceId = imageId, md5 = calculateImageMd5ByImageId(imageId)) override fun hashCode(): Int { @@ -156,6 +223,39 @@ data class NotOnlineImageFromFile( } override fun equals(other: Any?): Boolean { - return other is NotOnlineImageFromFile && other.md5.contentEquals(this.md5) && other.resourceId == this.resourceId + return other is OfflineFriendImage && other.md5 + .contentEquals(this.md5) && other.resourceId == this.resourceId } -} \ No newline at end of file +} + +/** + * 接收消息时获取到的 [FriendImage]. 它可以直接获取下载链接 [originUrl] + */ +abstract class OnlineFriendImage : FriendImage(), OnlineImage + + +// endregion + + +// region internal + +private val EMPTY_BYTE_ARRAY = ByteArray(0) + +private fun calculateImageMd5ByImageId(imageId: String): ByteArray { + return if (imageId.startsWith('/')) { + imageId + .drop(1) + .replace("-", "") + .take(16 * 2) + .chunkedHexToBytes() + } else { + imageId + .substringAfter('{') + .substringBefore('}') + .replace("-", "") + .take(16 * 2) + .chunkedHexToBytes() + } +} + +// endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Json.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Json.kt new file mode 100644 index 000000000..739bd907e --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Json.kt @@ -0,0 +1,27 @@ +/* + * 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.message.data + +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai + +/** + * Json 消息. + * + * @see LightApp 一些消息实际上是 [LightApp] + */ +@SinceMirai("0.27.0") +@OptIn(MiraiExperimentalAPI::class) +class JsonMessage(override val content: String) : RichMessage { + companion object Key : Message.Key + + // serviceId = 1 + override fun toString(): String = content +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/LightApp.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/LightApp.kt new file mode 100644 index 000000000..7c7277d67 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/LightApp.kt @@ -0,0 +1,24 @@ +/* + * 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.message.data + +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai + +/** + * 小程序分享, 如音乐分享 + */ +@OptIn(MiraiExperimentalAPI::class) +@SinceMirai("0.27.0") +class LightApp constructor(override val content: String) : RichMessage { + companion object Key : Message.Key + + override fun toString(): String = content +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt index 274020408..47f76c920 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/Message.kt @@ -7,17 +7,25 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE") +@file:Suppress("MemberVisibilityCanBePrivate", "unused", "EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.sendMessage +import net.mamoe.mirai.message.MessageReceipt +import kotlin.jvm.JvmSynthetic /** * 可发送的或从服务器接收的消息. * 采用这样的消息模式是因为 QQ 的消息多元化, 一条消息中可包含 [纯文本][PlainText], [图片][Image] 等. * + * [消息][Message] 分为 + * - [MessageMetadata] 消息元数据, 包括: [消息来源][MessageSource] + * - [MessageContent] 单个消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等. + * - [CombinedMessage] 通过 [plus] 连接的两个消息. 可通过 [asMessageChain] 转换为 [MessageChain] + * - [MessageChain] 不可变消息链, 即 [List] 形式链接的多个 [Message] 实例. + * * **在 Kotlin 使用 [Message]** * 这与使用 [String] 的使用非常类似. * @@ -41,8 +49,14 @@ import net.mamoe.mirai.contact.sendMessage * * @see PlainText 纯文本 * @see Image 图片 - * @see Face 表情 + * @see Face 原生表情 + * @see At 一个群成员的引用 + * @see AtAll 全体成员的引用 + * @see QuoteReply 一条消息的引用 + * * @see MessageChain 消息链(即 `List`) + * @see CombinedMessage 链接的两个消息 + * @see buildMessageChain 构造一个 [MessageChain] * * @see Contact.sendMessage 发送消息 */ @@ -56,7 +70,7 @@ interface Message { */ interface Key - infix fun eq(other: Message): Boolean = this == other + infix fun eq(other: Message): Boolean = this.toString() == other.toString() /** * 将 [toString] 与 [other] 比较 @@ -72,37 +86,69 @@ interface Message { * ```kotlin * val a = PlainText("Hello ") * val b = PlainText("world!") - * val c:MessageChain = a + b - * println(c)// "Hello world!" - * ``` + * val c: CombinedMessage = a + b + * println(c) // "Hello world!" * - * ```kotlin * val d = PlainText("world!") - * val e = c + d;//PlainText + MessageChain - * println(c)// "Hello world!" + * val e = c + d; // PlainText + CombinedMessage + * println(c) // "Hello world!" * ``` */ - fun followedBy(tail: Message): MessageChain { - require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } - require(this !is SingleOnly) { "SingleOnly Message cannot be followed" } - return if (tail is MessageChain) tail.followedBy(this)/*MessageChainImpl(this).also { tail.forEach { child -> it.concat(child) } }*/ - else MessageChainImpl(this, tail) + @JvmSynthetic // in java they should use `plus` instead + fun followedBy(tail: Message): CombinedMessage { + return CombinedMessage(tail, this) } override fun toString(): String - operator fun plus(another: Message): MessageChain = this.followedBy(another) - operator fun plus(another: String): MessageChain = this.followedBy(another.toMessage()) + operator fun plus(another: Message): CombinedMessage = this.followedBy(another) + + operator fun plus(another: String): CombinedMessage = this.followedBy(another.toMessage()) + // `+ ""` will be resolved to `plus(String)` instead of `plus(CharSeq)` - operator fun plus(another: CharSequence): MessageChain = this.followedBy(another.toString().toMessage()) + operator fun plus(another: CharSequence): CombinedMessage = this.followedBy(another.toString().toMessage()) +} + +suspend inline fun Message.sendTo(contact: C): MessageReceipt { + return contact.sendMessage(this) +} + +fun Message.repeat(count: Int): MessageChain { + return buildMessageChain(count) { + add(this@repeat) + } +} + +inline operator fun Message.times(count: Int): MessageChain = this.repeat(count) + +interface SingleMessage : Message + +/** + * 消息元数据, 即不含内容的元素. + * 包括: [MessageSource] + */ +interface MessageMetadata : SingleMessage { + /* + fun iterator(): Iterator { + return object : Iterator { + var visited: Boolean = false + override fun hasNext(): Boolean = !visited + override fun next(): Message { + if (visited) throw NoSuchElementException() + return this@MessageMetadata.also { visited = true } + } + } + }*/ } /** - * 表示这个 [Message] 仅能单个存在, 无法被连接. + * 消息内容 */ -interface SingleOnly : Message +interface MessageContent : SingleMessage /** * 将 [this] 发送给指定联系人 */ -suspend inline fun Message.sendTo(contact: Contact) = contact.sendMessage(this) \ No newline at end of file +@Suppress("UNCHECKED_CAST") +suspend inline fun MessageChain.sendTo(contact: C): MessageReceipt = + contact.sendMessage(this) as MessageReceipt \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt index 5e1ff2061..a12e28029 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageChain.kt @@ -7,151 +7,120 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") +@file:Suppress("unused", "NOTHING_TO_INLINE") + package net.mamoe.mirai.message.data +import net.mamoe.mirai.message.data.NullMessageChain.equals import net.mamoe.mirai.message.data.NullMessageChain.toString -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.contract +import net.mamoe.mirai.utils.MiraiInternalAPI import kotlin.js.JsName -import kotlin.jvm.Volatile +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.jvm.JvmSynthetic import kotlin.reflect.KProperty /** - * 消息链. 即 MutableList. - * 它的一般实现为 [MessageChainImpl], `null` 实现为 [NullMessageChain] - * - * 有关 [MessageChain] 的创建和连接: - * - 当任意两个不是 [MessageChain] 的 [Message] 相连接后, 将会产生一个 [MessageChain]. - * - 若两个 [MessageChain] 连接, 后一个将会被合并到第一个内. - * - 若一个 [MessageChain] 与一个其他 [Message] 连接, [Message] 将会被添加入 [MessageChain]. - * - 若一个 [Message] 与一个 [MessageChain] 连接, [Message] 将会被添加入 [MessageChain]. + * 消息链. + * 它的一般实现为 [MessageChainImplByIterable] 或 [MessageChainImplBySequence], + * 替代 `null` 情况的实现为 [NullMessageChain], + * 空的实现为 [EmptyMessageChain] * * 要获取更多信息, 请查看 [Message] + * + * @see buildMessageChain 构造一个 [MessageChain] + * @see asMessageChain 将单个 [Message] 转换为 [MessageChain] + * @see asMessageChain 将 [Iterable] 或 [Sequence] 委托为 [MessageChain] + * + * @see foreachContent 遍历内容 + * + * @see orNull 属性委托扩展 + * @see orElse 属性委托扩展 + * @see getValue 属性委托扩展 + * @see flatten 扁平化 */ -interface MessageChain : Message, MutableList { - // region Message override +interface MessageChain : Message, Iterable { override operator fun contains(sub: String): Boolean - override fun followedBy(tail: Message): MessageChain - // endregion - - operator fun plusAssign(message: Message) { - this.followedBy(message) - } - - operator fun plusAssign(plain: String) { - this.plusAssign(plain.toMessage()) - } - override fun toString(): String /** * 获取第一个类型为 [key] 的 [Message] 实例 * * @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key] + * @throws NoSuchElementException 当找不到这个类型的 [Message] 时 */ operator fun get(key: Message.Key): M = first(key) - override fun eq(other: Message): Boolean { - if(other is MessageChain && other.size != this.size) - return false - return this.toString() == other.toString() + /** + * 获取第一个类型为 [key] 的 [Message] 实例, 找不到则返回 `null` + * + * @param key 由各个类型消息的伴生对象持有. 如 [PlainText.Key] + */ + fun getOrNull(key: Message.Key): M? = firstOrNull(key) + + /** + * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]. + * 仅供 `Java` 使用 + */ + @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") + @JsName("forEachContent") + @JvmName("forEachContent") + @MiraiInternalAPI + fun `__forEachContent for Java__`(block: (Message) -> Unit) { + this.foreachContent(block) + } + + /** + * 遍历每一个消息, 即 [MessageSource] [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply]. + * 仅供 `Java` 使用 + */ + @Suppress("FunctionName", "INAPPLICABLE_JVM_NAME") + @JsName("forEach") + @JvmName("forEach") + @MiraiInternalAPI + fun `__forEach for Java__`(block: (Message) -> Unit) { + this.forEach(block) } } -/** - * 提供一个类型的值. 若不存在则会抛出异常 [NoSuchElementException] - */ -inline operator fun MessageChain.getValue(thisRef: Any?, property: KProperty<*>): T = this.first() +// region accessors /** - * 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 8 + * 遍历每一个有内容的消息, 即 [At], [AtAll], [PlainText], [Image], [Face], [XmlMessage], [QuoteReply] */ -@JsName("emptyMessageChain") -@Suppress("FunctionName") -fun MessageChain(): MessageChain = EmptyMessageChain() - -/** - * 构造无初始元素的可修改的 [MessageChain]. 初始大小将会被设定为 [initialCapacity] - */ -@Suppress("FunctionName") -fun MessageChain(initialCapacity: Int): MessageChain = - if (initialCapacity == 0) EmptyMessageChain() - else MessageChainImpl(ArrayList(initialCapacity)) - -/** - * 构造 [MessageChain] - * 若仅提供一个参数, 请考虑使用 [Message.toChain] 以优化性能 - */ -@Suppress("FunctionName") -fun MessageChain(vararg messages: Message): MessageChain = - if (messages.isEmpty()) EmptyMessageChain() - else MessageChainImpl(messages.toMutableList()) - -/** - * 构造 [MessageChain] - */ -@Suppress("FunctionName") -fun MessageChain(messages: Iterable): MessageChain = - MessageChainImpl(messages.toMutableList()) - -/** - * 构造单元素的不可修改的 [MessageChain]. 内部类实现为 [SingleMessageChain] - * - * 参数 [delegate] 不能为 [MessageChain] 的实例, 否则将会抛出异常. - * 使用 [Message.toChain] 将帮助提前处理这个问题. - * - * @param delegate 所构造的单元素 [MessageChain] 代表的 [Message] - * @throws IllegalArgumentException 当 [delegate] 为 [MessageChain] 的实例时 - * - * @see Message.toChain receiver 模式 - */ -@MiraiExperimentalAPI -@UseExperimental(ExperimentalContracts::class) -@Suppress("FunctionName") -fun SingleMessageChain(delegate: Message): MessageChain { - contract { - returns() implies (delegate !is MessageChain) +@JvmSynthetic +inline fun MessageChain.foreachContent(block: (Message) -> Unit) { + this.forEach { + if (it !is MessageMetadata) block(it) } - require(delegate !is MessageChain) { "delegate for SingleMessageChain should not be any instance of MessageChain" } - return SingleMessageChainImpl(delegate) } - -/** - * 得到包含 [this] 的 [MessageChain]. - * 若 [this] 为 [MessageChain] 将直接返回 this - * 否则将调用 [MessageChain] 构造一个 [MessageChainImpl] - */ -@Suppress("NOTHING_TO_INLINE") -inline fun Message.toChain(): MessageChain = if (this is MessageChain) this else MessageChain(this) - -/** - * 构造 [MessageChain] - */ -@Suppress("unused", "NOTHING_TO_INLINE") -inline fun List.toMessageChain(): MessageChain = MessageChain(this) - /** * 获取第一个 [M] 类型的 [Message] 实例 */ -inline fun MessageChain.firstOrNull(): M? = this.firstOrNull { it is M } as M? +@JvmSynthetic +inline fun MessageChain.firstOrNull(): M? = this.firstOrNull { it is M } as M? /** * 获取第一个 [M] 类型的 [Message] 实例 * @throws [NoSuchElementException] 如果找不到该类型的实例 */ +@JvmSynthetic inline fun MessageChain.first(): M = this.first { it is M } as M /** * 获取第一个 [M] 类型的 [Message] 实例 */ +@JvmSynthetic inline fun MessageChain.any(): Boolean = this.any { it is M } /** * 获取第一个 [M] 类型的 [Message] 实例 */ +@JvmSynthetic @Suppress("UNCHECKED_CAST") fun MessageChain.firstOrNull(key: Message.Key): M? = when (key) { At -> first() @@ -161,6 +130,10 @@ fun MessageChain.firstOrNull(key: Message.Key): M? = when (key) Face -> first() QuoteReply -> first() MessageSource -> first() + XmlMessage -> first() + JsonMessage -> first() + RichMessage -> first() + LightApp -> first() else -> null } as M? @@ -168,267 +141,307 @@ fun MessageChain.firstOrNull(key: Message.Key): M? = when (key) * 获取第一个 [M] 类型的 [Message] 实例 * @throws [NoSuchElementException] 如果找不到该类型的实例 */ +@JvmSynthetic @Suppress("UNCHECKED_CAST") -fun MessageChain.first(key: Message.Key): M = firstOrNull(key) ?: error("unknown key: $key") +fun MessageChain.first(key: Message.Key): M = + firstOrNull(key) ?: throw NoSuchElementException("no such element: $key") /** * 获取第一个 [M] 类型的 [Message] 实例 */ +@JvmSynthetic @Suppress("UNCHECKED_CAST") fun MessageChain.any(key: Message.Key): Boolean = firstOrNull(key) != null +// endregion accessors + + +// region delegate + /** - * 空的 [Message]. + * 提供一个类型的值的委托. 若不存在则会抛出异常 [NoSuchElementException] * - * 它不包含任何元素, 但维护一个 'lazy' 的 [MessageChainImpl]. + * 用法: + * ``` + * val message: MessageChain * - * 只有在必要的时候(如迭代([iterator]), 插入([add]), 连接([followedBy], [plus], [plusAssign]))才会创建这个对象代表的 list - * - * 它是一个正常的 [Message] 和 [MessageChain]. 可以做所有 [Message] 能做的事. + * val at: At by message + * val image: Image by message */ -class EmptyMessageChain : MessageChain { - private val delegate: MessageChain by lazy { - MessageChainImpl().also { initialized = true } - } +@JvmSynthetic +inline operator fun MessageChain.getValue(thisRef: Any?, property: KProperty<*>): T = this.first() - @Volatile - private var initialized: Boolean = false - - override fun subList(fromIndex: Int, toIndex: Int): MutableList = - if (initialized) delegate.subList( - fromIndex, - toIndex - ) else throw IndexOutOfBoundsException("given args that from $fromIndex to $toIndex, but the list is empty") - - override fun toString(): String = if (initialized) delegate.toString() else "" - - override fun contains(sub: String): Boolean = if (initialized) delegate.contains(sub) else false - override fun contains(element: Message): Boolean = if (initialized) delegate.contains(element) else false - override fun followedBy(tail: Message): MessageChain = delegate.followedBy(tail) - - override val size: Int = if (initialized) delegate.size else 0 - override fun containsAll(elements: Collection): Boolean = - if (initialized) delegate.containsAll(elements) else false - - override fun get(index: Int): Message = - if (initialized) delegate[index] else throw IndexOutOfBoundsException(index.toString()) - - override fun indexOf(element: Message): Int = if (initialized) delegate.indexOf(element) else -1 - override fun isEmpty(): Boolean = if (initialized) delegate.isEmpty() else true - override fun iterator(): MutableIterator = delegate.iterator() - - override fun lastIndexOf(element: Message): Int = if (initialized) delegate.lastIndexOf(element) else -1 - override fun add(element: Message): Boolean = delegate.add(element) - override fun add(index: Int, element: Message) = delegate.add(index, element) - override fun addAll(index: Int, elements: Collection): Boolean = delegate.addAll(elements) - override fun addAll(elements: Collection): Boolean = delegate.addAll(elements) - override fun clear() { - if (initialized) delegate.clear() - } - - override fun listIterator(): MutableListIterator = delegate.listIterator() - - override fun listIterator(index: Int): MutableListIterator = delegate.listIterator() - override fun remove(element: Message): Boolean = if (initialized) delegate.remove(element) else false - override fun removeAll(elements: Collection): Boolean = - if (initialized) delegate.removeAll(elements) else false - - override fun removeAt(index: Int): Message = - if (initialized) delegate.removeAt(index) else throw IndexOutOfBoundsException(index.toString()) - - override fun retainAll(elements: Collection): Boolean = - if (initialized) delegate.retainAll(elements) else false - - override fun set(index: Int, element: Message): Message = - if (initialized) delegate.set(index, element) else throw IndexOutOfBoundsException(index.toString()) +/** + * 可空的委托 + * @see orNull + */ +inline class OrNullDelegate(private val value: Any?) { + @Suppress("UNCHECKED_CAST") + operator fun getValue(thisRef: Any?, property: KProperty<*>): R = value as R } +/** + * 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null` + * + * 用法: + * ``` + * val message: MessageChain + * + * val at: At? by message.orNull() + * ``` + * @see orNull 提供一个不存在则 null 的委托 + * @see orElse 提供一个不存在则使用默认值的委托 + */ +@JvmSynthetic +inline fun MessageChain.orNull(): OrNullDelegate = OrNullDelegate(this.firstOrNull()) + +/** + * 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null` + * + * 用法: + * ``` + * val message: MessageChain + * + * val at: At by message.orElse { /* 返回一个 At */ } + * val atNullable: At? by message.orElse { /* 返回一个 At? */ } + * ``` + * @see orNull 提供一个不存在则 null 的委托 + */ +@Suppress("RemoveExplicitTypeArguments") +@JvmSynthetic +inline fun MessageChain.orElse( + lazyDefault: () -> T +): OrNullDelegate = + OrNullDelegate(this.firstOrNull() ?: lazyDefault()) + +// endregion delegate + + +// region converters +/** + * 得到包含 [this] 的 [MessageChain]. + * + * 若 [this] 为 [MessageChain] 将直接返回 this, + * 若 [this] 为 [CombinedMessage] 将 [扁平化][flatten] 后委托为 [MessageChain], + * 否则将调用 [asMessageChain] + */ +@JvmName("newChain") +@JsName("newChain") +@Suppress("UNCHECKED_CAST") +fun Message.asMessageChain(): MessageChain = when (this) { + is MessageChain -> this + is CombinedMessage -> (this as Iterable).asMessageChain() + else -> SingleMessageChainImpl(this as SingleMessage) +} + +/** + * 直接将 [this] 委托为一个 [MessageChain] + */ +@JvmSynthetic +fun Collection.asMessageChain(): MessageChain = MessageChainImplByCollection(this) + +/** + * 将 [this] [扁平化后][flatten] 委托为一个 [MessageChain] + */ +@JvmName("newChain") +// @JsName("newChain") +fun Collection.asMessageChain(): MessageChain = MessageChainImplBySequence(this.flatten()) + +/** + * 直接将 [this] 委托为一个 [MessageChain] + */ +@JvmSynthetic +fun Iterable.asMessageChain(): MessageChain = MessageChainImplByIterable(this) + +@JvmSynthetic +inline fun MessageChain.asMessageChain(): MessageChain = this // 避免套娃 + +@JvmSynthetic +fun CombinedMessage.asMessageChain(): MessageChain { + if (left is SingleMessage && this.element is SingleMessage) { + @Suppress("UNCHECKED_CAST") + return (this as Iterable).asMessageChain() + } + return (this as Iterable).asMessageChain() +} // 避免套娃 + +/** + * 将 [this] [扁平化后][flatten] 委托为一个 [MessageChain] + */ +@JvmName("newChain") +// @JsName("newChain") +fun Iterable.asMessageChain(): MessageChain = MessageChainImplBySequence(this.flatten()) + +/** + * 直接将 [this] 委托为一个 [MessageChain] + */ +@JvmSynthetic +fun Sequence.asMessageChain(): MessageChain = MessageChainImplBySequence(this) + +/** + * 将 [this] [扁平化后][flatten] 委托为一个 [MessageChain] + */ +@JvmName("newChain") +// @JsName("newChain") +fun Sequence.asMessageChain(): MessageChain = MessageChainImplBySequence(this.flatten()) + +/** + * 构造一个 [MessageChain] + * 为提供更好的 Java API. + */ +@Suppress("FunctionName") +@JvmName("newChain") +fun _____newChain______(vararg messages: Message): MessageChain { + return messages.asIterable().asMessageChain() +} + +/** + * 构造一个 [MessageChain] + * 为提供更好的 Java API. + */ +@Suppress("FunctionName") +@JvmName("newChain") +fun _____newChain______(messages: String): MessageChain { + return messages.toMessage().asMessageChain() +} + +/** + * 扁平化消息序列. + * + * 原 [this]: + * ``` + * A <- CombinedMessage(B, C) <- D <- MessageChain(E, F, G) + * ``` + * 结果 [Sequence]: + * ``` + * A <- B <- C <- D <- E <- F <- G + * ``` + */ +fun Iterable.flatten(): Sequence = asSequence().flatten() + +// @JsName("flatten1") +@JvmName("flatten1")// avoid platform declare clash +@JvmSynthetic +fun Iterable.flatten(): Sequence = this.asSequence() // fast path + +/** + * 扁平化消息序列. + * + * 原 [this]: + * ``` + * A <- CombinedMessage(B, C) <- D <- MessageChain(E, F, G) + * ``` + * 结果 [Sequence]: + * ``` + * A <- B <- C <- D <- E <- F <- G + * ``` + */ +fun Sequence.flatten(): Sequence = flatMap { it.flatten() } + +@JsName("flatten1") // avoid platform declare clash +@JvmName("flatten1") +@JvmSynthetic +fun Sequence.flatten(): Sequence = this // fast path + +/** + * 扁平化 [Message] + * + * 对于不同类型的接收者(receiver): + * - `CombinedMessage(A, B)` 返回 `A <- B` + * - `MessageChain(E, F, G)` 返回 `E <- F <- G` + * - 其他: 返回 `sequenceOf(this)` + */ +fun Message.flatten(): Sequence { + return when (this) { + is MessageChain -> this.asSequence() + is CombinedMessage -> this.flatten() + else -> sequenceOf(this as SingleMessage) + } +} + +fun CombinedMessage.flatten(): Sequence { + if (this.isFlat()){ + @Suppress("UNCHECKED_CAST") + return (this as Iterable).asSequence() + } else return this.asSequence().flatten() +} + +fun MessageChain.flatten(): Sequence = this.asSequence() // fast path + +// endregion converters + +// region implementations + +/** + * 不含任何元素的 [MessageChain] + */ +object EmptyMessageChain : MessageChain by MessageChainImplByIterable(emptyList()) + /** * Null 的 [MessageChain]. * 它不包含任何元素, 也没有创建任何 list. * - * 除 [toString] 外, 其他方法均 [error] + * 除 [toString] 和 [equals] 外, 其他方法均 [error] */ object NullMessageChain : MessageChain { - override fun subList(fromIndex: Int, toIndex: Int): MutableList = error("accessing NullMessageChain") - - override fun toString(): String = "null" - + override fun toString(): String = "NullMessageChain" + override fun equals(other: Any?): Boolean = other === this override fun contains(sub: String): Boolean = error("accessing NullMessageChain") - override fun contains(element: Message): Boolean = error("accessing NullMessageChain") - override fun followedBy(tail: Message): MessageChain = tail.toChain() - - override val size: Int get() = error("accessing NullMessageChain") - override fun containsAll(elements: Collection): Boolean = error("accessing NullMessageChain") - override fun get(index: Int): Message = error("accessing NullMessageChain") - override fun indexOf(element: Message): Int = error("accessing NullMessageChain") - override fun isEmpty(): Boolean = error("accessing NullMessageChain") - override fun iterator(): MutableIterator = error("accessing NullMessageChain") - - override fun lastIndexOf(element: Message): Int = error("accessing NullMessageChain") - override fun add(element: Message): Boolean = error("accessing NullMessageChain") - override fun add(index: Int, element: Message) = error("accessing NullMessageChain") - override fun addAll(index: Int, elements: Collection): Boolean = error("accessing NullMessageChain") - - override fun addAll(elements: Collection): Boolean = error("accessing NullMessageChain") - override fun clear() { - error("accessing NullMessageChain") - } - - override fun listIterator(): MutableListIterator = error("accessing NullMessageChain") - - override fun listIterator(index: Int): MutableListIterator = error("accessing NullMessageChain") - - override fun remove(element: Message): Boolean = error("accessing NullMessageChain") - override fun removeAll(elements: Collection): Boolean = error("accessing NullMessageChain") - override fun removeAt(index: Int): Message = error("accessing NullMessageChain") - override fun retainAll(elements: Collection): Boolean = error("accessing NullMessageChain") - override fun set(index: Int, element: Message): Message = error("accessing NullMessageChain") + override fun followedBy(tail: Message): CombinedMessage = CombinedMessage(left = EmptyMessageChain, element = tail) + override fun iterator(): MutableIterator = error("accessing NullMessageChain") } /** - * [MessageChain] 实现 - * 它是一个特殊的 [Message], 实现 [MutableList] 接口, 但将所有的接口调用都转到内部维护的另一个 [MutableList]. - */ -internal inline class MessageChainImpl constructor( - /** - * Elements will not be instances of [MessageChain] - */ - private val delegate: MutableList -) : Message, MutableList, // do not `by delegate`, bcz Inline class cannot implement an interface by delegation - MessageChain { - - constructor(vararg messages: Message) : this(messages.toMutableList()) - - // region Message override - override fun toString(): String = this.delegate.joinToString("") { it.toString() } - - override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } - override fun followedBy(tail: Message): MessageChain { - require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } - if (tail is MessageChain) tail.forEach { child -> this.followedBy(child) } - else this.delegate.add(tail) - return this - } - - // endregion - - // region MutableList override - override fun containsAll(elements: Collection): Boolean = delegate.containsAll(elements) - - override operator fun get(index: Int): Message = delegate[index] - override fun indexOf(element: Message): Int = delegate.indexOf(element) - override fun isEmpty(): Boolean = delegate.isEmpty() - override fun lastIndexOf(element: Message): Int = delegate.lastIndexOf(element) - override fun add(element: Message): Boolean = delegate.add(element) - override fun add(index: Int, element: Message) = delegate.add(index, element) - override fun addAll(index: Int, elements: Collection): Boolean = delegate.addAll(index, elements) - override fun addAll(elements: Collection): Boolean = delegate.addAll(elements) - override fun clear() = delegate.clear() - override fun listIterator(): MutableListIterator = delegate.listIterator() - override fun listIterator(index: Int): MutableListIterator = delegate.listIterator(index) - override fun remove(element: Message): Boolean = delegate.remove(element) - override fun removeAll(elements: Collection): Boolean = delegate.removeAll(elements) - override fun removeAt(index: Int): Message = delegate.removeAt(index) - override fun retainAll(elements: Collection): Boolean = delegate.retainAll(elements) - override fun set(index: Int, element: Message): Message = delegate.set(index, element) - override fun subList(fromIndex: Int, toIndex: Int): MutableList = delegate.subList(fromIndex, toIndex) - override operator fun iterator(): MutableIterator = delegate.iterator() - override operator fun contains(element: Message): Boolean = delegate.contains(element) - override val size: Int get() = delegate.size - // endregion -} - -/** - * 单个成员的不可修改的 [MessageChain]. - * - * 在连接时将会把它当做一个普通 [Message] 看待, 但它不能被 [plusAssign] + * 使用 [Iterable] 作为委托的 [MessageChain] */ @PublishedApi -internal inline class SingleMessageChainImpl( - private val delegate: Message -) : Message, MutableList, - MessageChain { +internal inline class MessageChainImplByIterable constructor( + private val delegate: Iterable +) : Message, Iterable, MessageChain { + override fun iterator(): Iterator = delegate.iterator() + override fun toString(): String = this.delegate.joinToString("") { it.toString() } + override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } +} - // region Message override - override operator fun contains(sub: String): Boolean = delegate.contains(sub) - override fun followedBy(tail: Message): MessageChain { - require(tail !is SingleOnly) { "SingleOnly Message cannot follow another message" } - return if (tail is MessageChain) tail.apply { followedBy(delegate) } - else MessageChain(delegate, tail) - } +/** + * 使用 [Collection] 作为委托的 [MessageChain] + */ +@PublishedApi +internal inline class MessageChainImplByCollection constructor( + private val delegate: Collection +) : Message, Iterable, MessageChain { + override fun iterator(): Iterator = delegate.iterator() + override fun toString(): String = this.delegate.joinToString("") { it.toString() } + override operator fun contains(sub: String): Boolean = delegate.any { it.contains(sub) } +} - override fun plusAssign(message: Message) = - throw UnsupportedOperationException("SingleMessageChainImpl cannot be plusAssigned") +/** + * 使用 [Iterable] 作为委托的 [MessageChain] + */ +@PublishedApi +internal class MessageChainImplBySequence constructor( + delegate: Sequence +) : Message, Iterable, MessageChain { + /** + * [Sequence] 可能只能消耗一遍, 因此需要先转为 [List] + */ + private val collected: List by lazy { delegate.toList() } - override fun toString(): String = delegate.toString() - // endregion + override fun iterator(): Iterator = collected.iterator() + override fun toString(): String = this.collected.joinToString("") { it.toString() } + override operator fun contains(sub: String): Boolean = collected.any { it.contains(sub) } +} - // region MutableList override - override fun containsAll(elements: Collection): Boolean = elements.all { it == delegate } +/** + * 单个 [SingleMessage] 作为 [MessageChain] + */ +@PublishedApi +internal inline class SingleMessageChainImpl constructor( + private val delegate: SingleMessage +) : Message, Iterable, MessageChain { + override fun toString(): String = this.delegate.toString() + override fun iterator(): Iterator = iterator { yield(delegate) } + override operator fun contains(sub: String): Boolean = sub in delegate +} - override operator fun get(index: Int): Message = if (index == 0) delegate else throw NoSuchElementException() - override fun indexOf(element: Message): Int = if (delegate == element) 0 else -1 - override fun isEmpty(): Boolean = false - override fun lastIndexOf(element: Message): Int = if (delegate == element) 0 else -1 - override fun add(element: Message): Boolean = throw UnsupportedOperationException() - override fun add(index: Int, element: Message) = throw UnsupportedOperationException() - override fun addAll(index: Int, elements: Collection): Boolean = throw UnsupportedOperationException() - override fun addAll(elements: Collection): Boolean = throw UnsupportedOperationException() - override fun clear() = throw UnsupportedOperationException() - override fun listIterator(): MutableListIterator = object : MutableListIterator { - private var hasNext = true - override fun hasPrevious(): Boolean = !hasNext - override fun nextIndex(): Int = if (hasNext) 0 else -1 - override fun previous(): Message = - if (hasPrevious()) { - hasNext = true - delegate - } else throw NoSuchElementException() - - override fun previousIndex(): Int = if (!hasNext) 0 else -1 - override fun add(element: Message) = throw UnsupportedOperationException() - override fun hasNext(): Boolean = hasNext - override fun next(): Message = - if (hasNext) { - hasNext = false - delegate - } else throw NoSuchElementException() - - override fun remove() = throw UnsupportedOperationException() - override fun set(element: Message) = throw UnsupportedOperationException() - } - - override fun listIterator(index: Int): MutableListIterator = - if (index == 0) listIterator() else throw UnsupportedOperationException() - - override fun remove(element: Message): Boolean = throw UnsupportedOperationException() - override fun removeAll(elements: Collection): Boolean = throw UnsupportedOperationException() - override fun removeAt(index: Int): Message = throw UnsupportedOperationException() - override fun retainAll(elements: Collection): Boolean = throw UnsupportedOperationException() - override fun set(index: Int, element: Message): Message = throw UnsupportedOperationException() - override fun subList(fromIndex: Int, toIndex: Int): MutableList { - return if (fromIndex == 0) when (toIndex) { - 1 -> mutableListOf(this) - 0 -> mutableListOf() - else -> throw UnsupportedOperationException() - } - else throw UnsupportedOperationException() - } - - override fun iterator(): MutableIterator = object : MutableIterator { - private var hasNext = true - override fun hasNext(): Boolean = hasNext - override fun next(): Message = - if (hasNext) { - hasNext = false - delegate - } else throw NoSuchElementException() - - override fun remove() = throw UnsupportedOperationException() - } - - override operator fun contains(element: Message): Boolean = element == delegate - override val size: Int get() = 1 - // endregion -} \ No newline at end of file +// endregion \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt index 3d1246dd5..d36d3579e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/MessageSource.kt @@ -7,41 +7,104 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + package net.mamoe.mirai.message.data +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.utils.LazyProperty +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + /** - * 消息源, 用于被引用. 它将由协议模块实现. - * 消息源只用于 [QuoteReply] + * 消息源, 它存在于 [MessageChain] 中, 用于表示这个消息的来源. + * + * 消息源只用于 [引用回复][QuoteReply] 或 [撤回][Bot.recall]. * * `mirai-core-qqandroid`: `net.mamoe.mirai.qqandroid.message.MessageSourceFromMsg` * + * @see Bot.recall 撤回一条消息 * @see MessageSource.quote 引用这条消息, 创建 [MessageChain] */ -interface MessageSource : Message { - companion object : Message.Key +interface MessageSource : Message, MessageMetadata { + companion object Key : Message.Key /** - * 实际上是个随机数, 但服务器确实是用它当做 uid + * 在 Mirai 中使用的 id. + * 高 32 位为 [sequenceId], + * 低 32 位为 [messageRandom] */ - val messageUid: Long + val id: Long /** - * 发送人号码 + * 等待 [sequenceId] 获取, 确保其可用. + * + * 这个方法 3 秒超时, 抛出 [IllegalStateException], 则表明原消息发送失败. + */ + suspend fun ensureSequenceIdAvailable() + + /** + * 发送时间, 单位为秒. 撤回好友消息时可能需要 + */ + val time: Long + + /** + * 发送人. 可以为机器人自己 */ val senderId: Long /** - * 群号码 + * 消息发送对象, 可以为一个群的 `uin` (非 `id`)或一个好友, 或机器人自己 + */ + val toUin: Long + + /** + * 当群消息时为群 id, [Group.id], 好友消息时为 0 */ val groupId: Long /** * 原消息内容 */ - val sourceMessage: MessageChain + @LazyProperty + val originalMessage: MessageChain /** * 固定返回空字符串 ("") */ override fun toString(): String -} \ No newline at end of file +} + +/** + * 序列号. 若是机器人发出去的消息, 请先 [确保 sequenceId 可用][MessageSource.ensureSequenceIdAvailable] + * @see MessageSource.id + */ +val MessageSource.sequenceId: Int get() = (this.id shr 32).toInt() + +/** + * 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. + * @see MessageSource.id + */ +val MessageSource.messageRandom: Int get() = this.id.toInt() + +// For MessageChain + +/** + * 消息 id. + * @see MessageSource.id + */ +val MessageChain.id: Long get() = this[MessageSource].id + +/** + * 消息序列号, 可能来自服务器也可以发送时赋值, 不唯一. + * @see MessageSource.id + */ +val MessageChain.sequenceId: Int get() = this[MessageSource].sequenceId + +/** + * 消息随机数. 由服务器或客户端指定后不能更改. 它是消息 id 的一部分. + * @see MessageSource.id + */ +val MessageChain.messageRandom: Int get() = this[MessageSource].messageRandom diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt index 6277da651..24dacdeb1 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/PlainText.kt @@ -7,20 +7,41 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") +@file:Suppress("NOTHING_TO_INLINE") + package net.mamoe.mirai.message.data +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName +import kotlin.jvm.JvmStatic + +/** + * 纯文本. 可含 emoji 表情. + * + * 一般不需要主动构造 [PlainText], [Message] 可直接与 [String] 相加. Java 用户请使用 [MessageChain.plus] + */ +inline class PlainText(val stringValue: String) : Message, MessageContent { + constructor(charSequence: CharSequence) : this(charSequence.toString()) -inline class PlainText(val stringValue: String) : Message { override operator fun contains(sub: String): Boolean = sub in stringValue override fun toString(): String = stringValue - companion object Key : Message.Key

+ companion object Key : Message.Key<PlainText> { + @JvmStatic + val Empty = PlainText("") - override fun eq(other: Message): Boolean { - if(other is MessageChain){ - return other eq this.toString() + @JvmStatic + val Null = PlainText("null") + + inline fun of(value: String): PlainText { + return PlainText(value) + } + + inline fun of(value: CharSequence): PlainText { + return PlainText(value) } - return other is PlainText && other.stringValue == this.stringValue } } @@ -28,15 +49,4 @@ inline class PlainText(val stringValue: String) : Message { * 构造 [PlainText] */ @Suppress("NOTHING_TO_INLINE") -inline fun String.toMessage(): PlainText = PlainText(this) - -/** - * 得到包含作为 [PlainText] 的 [this] 的 [MessageChain]. - * - * @return 唯一成员且不可修改的 [SingleMessageChainImpl] - * - * @see SingleMessageChain - * @see SingleMessageChainImpl - */ -@Suppress("NOTHING_TO_INLINE") -inline fun String.singleChain(): MessageChain = SingleMessageChainImpl(this.toMessage()) \ No newline at end of file +inline fun String.toMessage(): PlainText = PlainText(this) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt index 8921b1eca..b23371bf6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/QuoteReply.kt @@ -7,39 +7,68 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.Member +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.utils.MiraiInternalAPI +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName /** - * 群内的引用回复. - * 总是使用 [quote] 来构造实例. + * 从服务器接收的或客户端构造用来发送的群内的或好友的引用回复. + * + * 可以引用一条群消息并发送给一个好友, 或是引用好友消息发送给群. + * 可以引用自己发出的消息. 详见 [MessageReceipt.quote] + * + * 总是使用 [quote] 来构造这个实例. */ -class QuoteReply @MiraiInternalAPI constructor(val source: MessageSource) : Message { +open class QuoteReply +@MiraiInternalAPI constructor(val source: MessageSource) : Message, MessageContent { companion object Key : Message.Key<QuoteReply> override fun toString(): String = "" } /** - * 引用这条消息. - * 返回 `[QuoteReply] + [At] + [PlainText]`(必要的结构) + * 用于发送的引用回复. + * 总是使用 [quote] 来构造实例. */ -fun MessageChain.quote(sender: Member): MessageChain { +@OptIn(MiraiInternalAPI::class) +sealed class QuoteReplyToSend +@MiraiInternalAPI constructor(source: MessageSource) : QuoteReply(source) { + class ToGroup(source: MessageSource, val sender: QQ) : QuoteReplyToSend(source) { + fun createAt(): At = At(sender as Member) + } + + class ToFriend(source: MessageSource) : QuoteReplyToSend(source) +} + +/** + * 引用这条消息. + * @see sender 消息发送人. + */ +@OptIn(MiraiInternalAPI::class) +fun MessageChain.quote(sender: QQ?): QuoteReplyToSend { this.firstOrNull<MessageSource>()?.let { - @UseExperimental(MiraiInternalAPI::class) - return QuoteReply(it) + sender.at() + " " // required + return it.quote(sender) } error("cannot find MessageSource") } /** * 引用这条消息. - * 返回 `[QuoteReply] + [At] + [PlainText]`(必要的结构) + * @see from 消息来源. 若是好友发送 */ -fun MessageSource.quote(sender: Member): MessageChain { - @UseExperimental(MiraiInternalAPI::class) - return QuoteReply(this) + sender.at() + " " // required +@OptIn(MiraiInternalAPI::class) +fun MessageSource.quote(from: QQ?): QuoteReplyToSend { + return if (this.groupId != 0L) { + check(from is Member) { "sender must be Member to quote a GroupMessage" } + QuoteReplyToSend.ToGroup(this, from) + } else QuoteReplyToSend.ToFriend(this) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/EventPacket.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt similarity index 55% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/EventPacket.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt index 630db03e1..af967da8e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/data/EventPacket.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/RichMessage.kt @@ -7,13 +7,20 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai.data +package net.mamoe.mirai.message.data -import net.mamoe.mirai.event.Event +import net.mamoe.mirai.utils.SinceMirai /** - * 事件包. 可被监听. + * XML 消息等富文本消息 * - * @see Event + * @see XmlMessage + * @see JsonMessage + * @see LightApp */ -interface EventPacket : Event, Packet \ No newline at end of file +@SinceMirai("0.27.0") +interface RichMessage : MessageContent { + companion object Key : Message.Key<RichMessage> + + val content: String +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt index 906b039e9..339f4b483 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/XML.kt @@ -7,33 +7,42 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmMultifileClass +@file:JvmName("MessageUtils") + @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.message.data +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.SinceMirai +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + /** * XML 消息, 如分享, 卡片等. * * @see buildXMLMessage */ -inline class XMLMessage(val stringValue: String) : Message, - SingleOnly { - override fun followedBy(tail: Message): Nothing = error("XMLMessage Message cannot be followed") - override fun toString(): String = stringValue +@SinceMirai("0.27.0") +@OptIn(MiraiExperimentalAPI::class) +class XmlMessage constructor(override val content: String) : RichMessage { + companion object Key : Message.Key<XmlMessage> - override fun eq(other: Message): Boolean { - return other is XMLMessage && other.stringValue == this.stringValue - } + // override val serviceId: Int get() = 60 + + override fun toString(): String = content } /** * 构造一条 XML 消息 */ -@XMLDsl -inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XMLMessage = - XMLMessage(XMLMessageBuilder().apply(block).text) +@SinceMirai("0.27.0") +@MiraiExperimentalAPI +inline fun buildXMLMessage(block: @XMLDsl XMLMessageBuilder.() -> Unit): XmlMessage = + XmlMessage(XMLMessageBuilder().apply(block).text) -@Suppress("NOTHING_TO_INLINE") +@SinceMirai("0.27.0") @XMLDsl class ItemBuilder( var bg: Int = 0, @@ -43,21 +52,20 @@ class ItemBuilder( internal val builder: StringBuilder = StringBuilder() val text: String get() = "<item bg='$bg' layout='$layout'>$builder</item>" - inline fun summary(text: String, color: String = "#FFFFFF") { + fun summary(text: String, color: String = "#000000") { this.builder.append("<summary color='$color'>$text</summary>") } - inline fun title(text: String, size: Int = 18, color: String = "#FFFFFF") { + fun title(text: String, size: Int = 25, color: String = "#000000") { this.builder.append("<title size='$size' color='$color'>$text</title>") } - inline fun picture(coverUrl: String) { + fun picture(coverUrl: String) { this.builder.append("<picture cover='$coverUrl'/>") } } @XMLDsl -@Suppress("NOTHING_TO_INLINE") class XMLMessageBuilder( var templateId: Int = 1, var serviceId: Int = 1, @@ -67,7 +75,7 @@ class XMLMessageBuilder( */ var actionData: String = "", /** - * 摘要 + * 摘要, 在官方客户端内消息列表中显示 */ var brief: String = "", var flag: Int = 3, @@ -86,16 +94,39 @@ class XMLMessageBuilder( "</msg>" @XMLDsl - inline fun item(block: @XMLDsl ItemBuilder.() -> Unit) { + fun item(block: @XMLDsl ItemBuilder.() -> Unit) { builder.append(ItemBuilder().apply(block).text) } - inline fun source(name: String, iconURL: String = "") { + fun source(name: String, iconURL: String = "") { sourceName = name sourceIconURL = iconURL } } +@MiraiExperimentalAPI +object XmlMessageHelper { + fun share(u: String, title: String?, content: String?, image: String?) = buildXMLMessage { + templateId = 12345 + serviceId = 1 + action = "web" + brief = "[分享] " + (title ?: "") + url = u + item { + layout = 2 + if (image != null) { + picture(image) + } + if (title != null) { + title(title) + } + if (content != null) { + summary(content) + } + } + } +} + @Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) @DslMarker -internal annotation class XMLDsl \ No newline at end of file +annotation class XMLDsl diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt new file mode 100644 index 000000000..d61690477 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/message/data/builder.kt @@ -0,0 +1,118 @@ +/* + * 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.message.data + +import kotlin.jvm.JvmOverloads + +/** + * 构建一个 [MessageChain] + * + * @see MessageChainBuilder + */ +inline fun buildMessageChain(block: MessageChainBuilder.() -> Unit): MessageChain { + return MessageChainBuilder().apply(block).asMessageChain() +} + +/** + * 使用特定的容器大小构建一个 [MessageChain] + * + * @see MessageChainBuilder + */ +inline fun buildMessageChain(initialSize: Int, block: MessageChainBuilder.() -> Unit): MessageChain { + return MessageChainBuilder(initialSize).apply(block).asMessageChain() +} + +/** + * 使用特定的容器构建一个 [MessageChain] + * + * @see MessageChainBuilder + */ +inline fun buildMessageChain( + container: MutableCollection<Message>, + block: MessageChainBuilder.() -> Unit +): MessageChain { + return MessageChainBuilder(container).apply(block).asMessageChain() +} + +/** + * [MessageChain] 构建器. + * + * @see buildMessageChain 推荐使用 + * @see asMessageChain 完成构建 + */ +class MessageChainBuilder +@JvmOverloads constructor( + private val container: MutableCollection<Message> = mutableListOf() +) : MutableCollection<Message> by container, Appendable { + constructor(initialSize: Int) : this(ArrayList<Message>(initialSize)) + + override fun add(element: Message): Boolean { + flushCache() + return container.add(element) + } + + override fun addAll(elements: Collection<Message>): Boolean { + flushCache() + return container.addAll(elements) + } + + operator fun Message.unaryPlus() { + flushCache() + add(this) + } + + operator fun String.unaryPlus() { + add(this) + } + + operator fun plusAssign(plain: String) { + withCache { append(plain) } + } + + operator fun plusAssign(message: Message) { + flushCache() + this.add(message) + } + + fun add(plain: String) { + withCache { append(plain) } + } + + private var cache: StringBuilder? = null + private fun flushCache() { + cache?.let { + container.add(it.toString().toMessage()) + } + cache = null + } + + private inline fun withCache(block: StringBuilder.() -> Unit): MessageChainBuilder { + if (cache == null) { + cache = StringBuilder().apply(block) + } else { + cache!!.apply(block) + } + return this + } + + operator fun plusAssign(charSequence: CharSequence) { + withCache { append(charSequence) } + } + + override fun append(value: Char): Appendable = withCache { append(value) } + override fun append(value: CharSequence?): Appendable = withCache { append(value) } + override fun append(value: CharSequence?, startIndex: Int, endIndex: Int) = + withCache { append(value, startIndex, endIndex) } + + fun asMessageChain(): MessageChain { + this.flushCache() + return (this as MutableCollection<Message>).asMessageChain() + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt index 76ffb50d1..01edbd102 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/network/BotNetworkHandler.kt @@ -68,7 +68,7 @@ abstract class BotNetworkHandler : CoroutineScope { */ @Suppress("SpellCheckingInspection") @MiraiInternalAPI - abstract suspend fun relogin() + abstract suspend fun relogin(cause: Throwable? = null) /** * 初始化获取好友列表等值. @@ -80,9 +80,10 @@ abstract class BotNetworkHandler : CoroutineScope { } /** - * 等待直到与服务器断开连接. 若未连接则立即返回 + * 当 [Bot] 正常运作时, 这个函数将一直挂起协程到 [Bot] 被 [Bot.close] + * 当 [Bot] 离线时, 这个函数立即返回. */ - abstract suspend fun awaitDisconnection() + abstract suspend fun join() /** * 关闭网络接口, 停止所有有关协程和任务 @@ -100,7 +101,7 @@ abstract class BotNetworkHandler : CoroutineScope { } } -@UseExperimental(MiraiInternalAPI::class) +@OptIn(MiraiInternalAPI::class) suspend fun BotNetworkHandler.closeAndJoin(cause: Throwable? = null) { this.close(cause) this.supervisor.join() diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt index 3ebb748ec..49a691efe 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/BotConfiguration.kt @@ -9,7 +9,6 @@ package net.mamoe.mirai.utils -import kotlinx.io.core.IoBuffer import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler import kotlin.coroutines.CoroutineContext @@ -18,18 +17,17 @@ import kotlin.jvm.JvmStatic /** * 验证码, 设备锁解决器 */ -abstract class LoginSolver { - abstract suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? +expect abstract class LoginSolver { + abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? -} -/** - * 在各平台实现的默认的验证码处理器. - */ -expect var defaultLoginSolver: LoginSolver + companion object { + val Default: LoginSolver + } +} /** * [Bot] 配置 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt index cf911eff2..2000b3889 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/DeviceInfo.kt @@ -9,10 +9,10 @@ package net.mamoe.mirai.utils -import kotlinx.serialization.SerialId import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.protobuf.ProtoBuf +import kotlinx.serialization.protobuf.ProtoId /** * 设备信息. 可通过继承 [SystemDeviceInfo] 来在默认的基础上修改 @@ -60,15 +60,15 @@ abstract class DeviceInfo { fun generateDeviceInfoData(): ByteArray { @Serializable class DevInfo( - @SerialId(1) val bootloader: ByteArray, - @SerialId(2) val procVersion: ByteArray, - @SerialId(3) val codename: ByteArray, - @SerialId(4) val incremental: ByteArray, - @SerialId(5) val fingerprint: ByteArray, - @SerialId(6) val bootId: ByteArray, - @SerialId(7) val androidId: ByteArray, - @SerialId(8) val baseBand: ByteArray, - @SerialId(9) val innerVersion: ByteArray + @ProtoId(1) val bootloader: ByteArray, + @ProtoId(2) val procVersion: ByteArray, + @ProtoId(3) val codename: ByteArray, + @ProtoId(4) val incremental: ByteArray, + @ProtoId(5) val fingerprint: ByteArray, + @ProtoId(6) val bootId: ByteArray, + @ProtoId(7) val androidId: ByteArray, + @ProtoId(8) val baseBand: ByteArray, + @ProtoId(9) val innerVersion: ByteArray ) return ProtoBuf.dump( @@ -94,6 +94,7 @@ abstract class DeviceInfo { } } +@OptIn(MiraiInternalAPI::class) @Serializable class DeviceInfoData( override val display: ByteArray, @@ -120,9 +121,11 @@ class DeviceInfoData( @Transient override lateinit var context: Context - @UseExperimental(ExperimentalUnsignedTypes::class) + @OptIn(ExperimentalUnsignedTypes::class) override val ipAddress: ByteArray - get() = localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 }?.toByteArray() ?: byteArrayOf() + get() = MiraiPlatformUtils.localIpAddress().split(".").map { it.toUByte().toByte() }.takeIf { it.size == 4 } + ?.toByteArray() + ?: byteArrayOf() override val androidId: ByteArray get() = display @Serializable @@ -133,10 +136,13 @@ class DeviceInfoData( override val sdk: Int = SystemDeviceInfo.Version.sdk ) : Version } + /** * Defaults "%4;7t>;28<fc.5*6".toByteArray() */ -fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray = md5(androidId + macAddress) +@OptIn(MiraiInternalAPI::class) +fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray = + MiraiPlatformUtils.md5(androidId + macAddress) /* fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo( diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt index d389e4119..97ccfb37e 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/ExternalImage.kt @@ -11,14 +11,19 @@ package net.mamoe.mirai.utils +import kotlinx.coroutines.io.ByteReadChannel +import kotlinx.io.InputStream import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Input import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.OfflineImage import net.mamoe.mirai.message.data.sendTo import net.mamoe.mirai.utils.io.toUHexString +import kotlinx.serialization.InternalSerializationApi /** * 外部图片. 图片数据还没有读取到内存. @@ -28,32 +33,92 @@ import net.mamoe.mirai.utils.io.toUHexString * @see ExternalImage.sendTo 上传图片并以纯图片消息发送给联系人 * @See ExternalImage.upload 上传图片并得到 [Image] 消息 */ -class ExternalImage( +class ExternalImage private constructor( val width: Int, val height: Int, val md5: ByteArray, imageFormat: String, - val input: Input, + val input: Any, // Input from kotlinx.io, InputStream from kotlinx.io MPP, ByteReadChannel from ktor val inputSize: Long, // dont be greater than Int.MAX val filename: String ) { + constructor( + width: Int, + height: Int, + md5: ByteArray, + imageFormat: String, + input: ByteReadChannel, + inputSize: Long, // dont be greater than Int.MAX + filename: String + ) : this(width, height, md5, imageFormat, input as Any, inputSize, filename) + + constructor( + width: Int, + height: Int, + md5: ByteArray, + imageFormat: String, + input: Input, + inputSize: Long, // dont be greater than Int.MAX + filename: String + ) : this(width, height, md5, imageFormat, input as Any, inputSize, filename) + + constructor( + width: Int, + height: Int, + md5: ByteArray, + imageFormat: String, + input: ByteReadPacket, + filename: String + ) : this(width, height, md5, imageFormat, input as Any, input.remaining, filename) + + @OptIn(InternalSerializationApi::class) + constructor( + width: Int, + height: Int, + md5: ByteArray, + imageFormat: String, + input: InputStream, + filename: String + ) : this(width, height, md5, imageFormat, input as Any, input.available().toLong(), filename) + init { - check(inputSize in 0L..Int.MAX_VALUE.toLong()) { "file is too big" } + require(inputSize < 30L * 1024 * 1024) { "file is too big. Maximum is about 20MB" } } companion object { - operator fun invoke( - width: Int, - height: Int, - md5: ByteArray, - format: String, - data: ByteReadPacket, - filename: String - ): ExternalImage = ExternalImage(width, height, md5, format, data, data.remaining, filename) - - fun generateUUID(md5: ByteArray): String{ + fun generateUUID(md5: ByteArray): String { return "${md5[0..3]}-${md5[4..5]}-${md5[6..7]}-${md5[8..9]}-${md5[10..15]}" } + + fun generateImageId(md5: ByteArray, imageType: Int): String { + return """{${generateUUID(md5)}}.${determineFormat(imageType)}""" + } + + fun determineImageType(format: String): Int { + return when (format) { + "jpg" -> 1000 + "png" -> 1001 + "webp" -> 1002 + "bmp" -> 1005 + "gig" -> 2000 + "apng" -> 2001 + "sharpp" -> 1004 + else -> 1000 // unsupported, just make it jpg + } + } + + fun determineFormat(imageType: Int): String { + return when (imageType) { + 1000 -> "jpg" + 1001 -> "png" + 1002 -> "webp" + 1005 -> "bmp" + 2000 -> "gig" + 2001 -> "apng" + 1004 -> "sharpp" + else -> "jpg" // unsupported, just make it jpg + } + } } val format: String = @@ -73,16 +138,7 @@ class ExternalImage( * SHARPP: 1004 */ val imageType: Int - get() = when (format) { - "jpg" -> 1000 - "png" -> 1001 - "webp" -> 1002 - "bmp" -> 1005 - "gig" -> 2000 - "apng" -> 2001 - "sharpp" -> 1004 - else -> 1000 // unsupported, just make it jpg - } + get() = determineImageType(format) override fun toString(): String = "[ExternalImage(${width}x$height $format)]" @@ -94,7 +150,7 @@ class ExternalImage( /** * 将图片发送给指定联系人 */ -suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) { +suspend fun <C : Contact> ExternalImage.sendTo(contact: C): MessageReceipt<C> = when (contact) { is Group -> contact.uploadImage(this).sendTo(contact) is QQ -> contact.uploadImage(this).sendTo(contact) else -> error("unreachable") @@ -106,7 +162,7 @@ suspend fun ExternalImage.sendTo(contact: Contact) = when (contact) { * * @see contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人 */ -suspend fun ExternalImage.upload(contact: Contact): Image = when (contact) { +suspend fun ExternalImage.upload(contact: Contact): OfflineImage = when (contact) { is Group -> contact.uploadImage(this) is QQ -> contact.uploadImage(this) else -> error("unreachable") @@ -115,7 +171,7 @@ suspend fun ExternalImage.upload(contact: Contact): Image = when (contact) { /** * 将图片发送给 [this] */ -suspend inline fun Contact.sendImage(image: ExternalImage) = image.sendTo(this) +suspend inline fun <C : Contact> C.sendImage(image: ExternalImage): MessageReceipt<C> = image.sendTo(this) private operator fun ByteArray.get(range: IntRange): String = buildString { range.forEach { diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/MiraiEnvironment.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LazyProperty.kt similarity index 63% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/MiraiEnvironment.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LazyProperty.kt index ea33d13e1..655765eed 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/MiraiEnvironment.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LazyProperty.kt @@ -7,19 +7,11 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package net.mamoe.mirai +package net.mamoe.mirai.utils /** - * 平台相关环境属性 + * 表示这个属性由 [lazy] 委托, 即它只会在被需要的时候才初始化. */ -expect object MiraiEnvironment { - val platform: Platform -} - -/** - * 可用平台列表 - */ -enum class Platform { - ANDROID, - JVM -} \ No newline at end of file +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.BINARY) +annotation class LazyProperty \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt index 0212d8a48..8ab9ed5cc 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/LockFreeLinkedList.kt @@ -15,18 +15,6 @@ import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.atomicfu.loop -inline fun <E> LockFreeLinkedList<E>.joinToString( - separator: CharSequence = ", ", - prefix: CharSequence = "[", - postfix: CharSequence = "]", - transform: ((E) -> CharSequence) = { it.toString() } -): String = prefix.toString() + buildString { - this@joinToString.forEach { - append(transform(it)) - append(separator) - } -}.dropLast(separator.length) + postfix - /** * Collect all the elements into a [MutableList] then cast it as a [List] */ @@ -68,6 +56,10 @@ fun <E> LockFreeLinkedList<E>.asSequence(): Sequence<E> { } } +operator fun <E> LockFreeLinkedList<E>.iterator(): Iterator<E> { + return asSequence().iterator() +} + /** * 构建链表结构然后转为 [LockFreeLinkedList] */ @@ -107,9 +99,14 @@ open class LockFreeLinkedList<E> { } } - open fun peekFirst(): E = head.nextNode.letValueIfValid { return it } ?: throw NoSuchElementException() - - open fun peekLast(): E = head.iterateBeforeFirst { it === tail }.letValueIfValid { return it } ?: throw NoSuchElementException() + open fun peekFirst(): E { + return head + .iterateBeforeFirst { it.isValidElementNode() } + .takeUnless { it.isTail() } + ?.nextNode + ?.nodeValue + ?: throw NoSuchElementException() + } open fun removeLast(): E { while (true) { @@ -128,7 +125,7 @@ open class LockFreeLinkedList<E> { addLastNode(element.asNode(tail)) } - private fun addLastNode(node: Node<E>) { + private fun addLastNode(node: LockFreeLinkedListNode<E>) { while (true) { val tail = head.iterateBeforeFirst { it === tail } // find the last node. if (tail.nextNodeRef.compareAndSet(this.tail, node)) { // ensure the last node is the last node @@ -142,9 +139,9 @@ open class LockFreeLinkedList<E> { */ @Suppress("DuplicatedCode") open fun addAll(iterable: Iterable<E>) { - var firstNode: Node<E>? = null + var firstNode: LockFreeLinkedListNode<E>? = null - var currentNode: Node<E>? = null + var currentNode: LockFreeLinkedListNode<E>? = null iterable.forEach { val nextNode = it.asNode(tail) if (firstNode == null) { @@ -162,9 +159,9 @@ open class LockFreeLinkedList<E> { */ @Suppress("DuplicatedCode") open fun addAll(iterable: Sequence<E>) { - var firstNode: Node<E>? = null + var firstNode: LockFreeLinkedListNode<E>? = null - var currentNode: Node<E>? = null + var currentNode: LockFreeLinkedListNode<E>? = null iterable.forEach { val nextNode = it.asNode(tail) if (firstNode == null) { @@ -186,7 +183,7 @@ open class LockFreeLinkedList<E> { val node = LazyNode(tail, supplier) while (true) { - var current: Node<E> = head + var current: LockFreeLinkedListNode<E> = head findLastNode@ while (true) { if (current.isValidElementNode() && filter(current.nodeValue)) @@ -204,13 +201,14 @@ open class LockFreeLinkedList<E> { } @PublishedApi // limitation by atomicfu - internal fun <E> Node<E>.compareAndSetNextNodeRef(expect: Node<E>, update: Node<E>) = this.nextNodeRef.compareAndSet(expect, update) + internal fun <E> LockFreeLinkedListNode<E>.compareAndSetNextNodeRef(expect: LockFreeLinkedListNode<E>, update: LockFreeLinkedListNode<E>) = + this.nextNodeRef.compareAndSet(expect, update) - override fun toString(): String = joinToString() + override fun toString(): String = "[" + asSequence().joinToString() + "]" @Suppress("unused") internal fun getLinkStructure(): String = buildString { - head.childIterateReturnsLastSatisfying<Node<*>>({ + head.childIterateReturnsLastSatisfying<LockFreeLinkedListNode<*>>({ append(it.toString()) append(" <- ") it.nextNode @@ -236,7 +234,7 @@ open class LockFreeLinkedList<E> { // physically remove: try to fix the link - var next: Node<E> = toRemove.nextNode + var next: LockFreeLinkedListNode<E> = toRemove.nextNode while (next !== tail && next.isRemoved()) { next = next.nextNode } @@ -265,7 +263,7 @@ open class LockFreeLinkedList<E> { // physically remove: try to fix the link - var next: Node<E> = toRemove.nextNode + var next: LockFreeLinkedListNode<E> = toRemove.nextNode while (next !== tail && next.isRemoved()) { next = next.nextNode } @@ -278,7 +276,7 @@ open class LockFreeLinkedList<E> { /** * 动态计算的大小 */ - val size: Int get() = head.countChildIterate<Node<E>>({ it.nextNode }, { it !is Tail }) - 1 // empty head is always included + val size: Int get() = head.countChildIterate<LockFreeLinkedListNode<E>>({ it.nextNode }, { it !is Tail }) - 1 // empty head is always included open operator fun contains(element: E): Boolean { forEach { if (it == element) return true } @@ -291,7 +289,7 @@ open class LockFreeLinkedList<E> { open fun isEmpty(): Boolean = head.allMatching { it.isValidElementNode().not() } inline fun forEach(block: (E) -> Unit) { - var node: Node<E> = head + var node: LockFreeLinkedListNode<E> = head while (true) { if (node === tail) return node.letValueIfValid(block) @@ -299,6 +297,15 @@ open class LockFreeLinkedList<E> { } } + inline fun forEachNode(block: (LockFreeLinkedListNode<E>) -> Unit) { + var node: LockFreeLinkedListNode<E> = head + while (true) { + if (node === tail) return + node.letValueIfValid { block(node) } + node = node.nextNode + } + } + @Suppress("unused") open fun clear() { val first = head.nextNode @@ -634,14 +641,14 @@ open class LockFreeLinkedList<E> { // region internal @Suppress("NOTHING_TO_INLINE") -private inline fun <E> E.asNode(nextNode: Node<E>): Node<E> = Node(nextNode, this) +private inline fun <E> E.asNode(nextNode: LockFreeLinkedListNode<E>): LockFreeLinkedListNode<E> = LockFreeLinkedListNode(nextNode, this) /** * Self-iterate using the [iterator], until [mustBeTrue] returns `false`. * Returns the element at the last time when the [mustBeTrue] returns `true` */ @PublishedApi -internal inline fun <N : Node<*>> N.childIterateReturnsLastSatisfying(iterator: (N) -> N, mustBeTrue: (N) -> Boolean): N { +internal inline fun <N : LockFreeLinkedListNode<*>> N.childIterateReturnsLastSatisfying(iterator: (N) -> N, mustBeTrue: (N) -> Boolean): N { if (!mustBeTrue(this)) return this var value: N = this @@ -699,9 +706,9 @@ private inline fun <E> E.countChildIterate(iterator: (E) -> E, mustBeTrue: (E) - @PublishedApi internal class LazyNode<E> @PublishedApi internal constructor( - nextNode: Node<E>, + nextNode: LockFreeLinkedListNode<E>, private val valueComputer: () -> E -) : Node<E>(nextNode, null) { +) : LockFreeLinkedListNode<E>(nextNode, null) { private val initialized = atomic(false) private val value: AtomicRef<E?> = atomic(null) @@ -723,21 +730,20 @@ internal class LazyNode<E> @PublishedApi internal constructor( } @PublishedApi -internal class Head<E>(nextNode: Node<E>) : Node<E>(nextNode, null) { +internal class Head<E>(nextNode: LockFreeLinkedListNode<E>) : LockFreeLinkedListNode<E>(nextNode, null) { override fun toString(): String = "Head" override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Head") } @PublishedApi -internal open class Tail<E> : Node<E>(null, null) { +internal open class Tail<E> : LockFreeLinkedListNode<E>(null, null) { override fun toString(): String = "Tail" override val nodeValue: Nothing get() = error("Internal error: trying to get the value of a Tail") } -@PublishedApi -internal open class Node<E>( - nextNode: Node<E>?, - private var initialNodeValue: E? +open class LockFreeLinkedListNode<E>( + nextNode: LockFreeLinkedListNode<E>?, + private val initialNodeValue: E? ) { /* internal val id: Int = nextId() @@ -750,23 +756,21 @@ internal open class Node<E>( open val nodeValue: E get() = initialNodeValue ?: error("Internal error: nodeValue is not initialized") - val removed = atomic(false) + @PublishedApi + internal val removed = atomic(false) @Suppress("LeakingThis") - val nextNodeRef: AtomicRef<Node<E>> = atomic(nextNode ?: this) + internal val nextNodeRef: AtomicRef<LockFreeLinkedListNode<E>> = atomic(nextNode ?: this) inline fun <R> letValueIfValid(block: (E) -> R): R? { - if (!this.isValidElementNode()) { - return null - } - val value = this.nodeValue - return if (value !== null) block(value) else null + return this.takeIf { isValidElementNode() }?.nodeValue?.let(block) } /** * Short cut for accessing [nextNodeRef] */ - var nextNode: Node<E> + @PublishedApi + internal var nextNode: LockFreeLinkedListNode<E> get() = nextNodeRef.value set(value) { nextNodeRef.value = value @@ -775,7 +779,7 @@ internal open class Node<E>( /** * Returns the former node of the last node whence [filter] returns true */ - inline fun iterateBeforeFirst(filter: (Node<E>) -> Boolean): Node<E> = + inline fun iterateBeforeFirst(filter: (LockFreeLinkedListNode<E>) -> Boolean): LockFreeLinkedListNode<E> = this.childIterateReturnsLastSatisfying({ it.nextNode }, { !filter(it) }) /** @@ -784,7 +788,8 @@ internal open class Node<E>( * Head, which is this, is also being tested. * [Tail], is not being tested. */ - inline fun allMatching(condition: (Node<E>) -> Boolean): Boolean = this.childIterateReturnsLastSatisfying({ it.nextNode }, condition) !is Tail + inline fun allMatching(condition: (LockFreeLinkedListNode<E>) -> Boolean): Boolean = + this.childIterateReturnsLastSatisfying({ it.nextNode }, condition) !is Tail /** * Stop on and returns the former element of the element that is [equals] to the [element] @@ -792,23 +797,23 @@ internal open class Node<E>( * E.g.: for `head <- 1 <- 2 <- 3 <- tail`, `iterateStopOnNodeValue(2)` returns the node whose value is 1 */ @Suppress("NOTHING_TO_INLINE") - internal inline fun iterateBeforeNodeValue(element: E): Node<E> = this.iterateBeforeFirst { it.isValidElementNode() && it.nodeValue == element } + internal inline fun iterateBeforeNodeValue(element: E): LockFreeLinkedListNode<E> = + this.iterateBeforeFirst { it.isValidElementNode() && it.nodeValue == element } } -@PublishedApi // DO NOT INLINE: ATOMIC OPERATION -internal fun <E> Node<E>.isRemoved() = this.removed.value +fun <E> LockFreeLinkedListNode<E>.isRemoved() = this.removed.value @PublishedApi @Suppress("NOTHING_TO_INLINE") -internal inline fun Node<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved() +internal inline fun LockFreeLinkedListNode<*>.isValidElementNode(): Boolean = !isHead() && !isTail() && !isRemoved() @PublishedApi @Suppress("NOTHING_TO_INLINE") -internal inline fun Node<*>.isHead(): Boolean = this is Head +internal inline fun LockFreeLinkedListNode<*>.isHead(): Boolean = this is Head @PublishedApi @Suppress("NOTHING_TO_INLINE") -internal inline fun Node<*>.isTail(): Boolean = this is Tail +internal inline fun LockFreeLinkedListNode<*>.isTail(): Boolean = this is Tail // end region \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt index 068e67d4a..19303e301 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/MiraiLogger.kt @@ -8,10 +8,14 @@ */ @file:Suppress("unused") +@file:JvmMultifileClass +@file:JvmName("Utils") package net.mamoe.mirai.utils import net.mamoe.mirai.Bot +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads @@ -70,6 +74,13 @@ interface MiraiLogger { */ val identity: String? + /** + * 获取 [MiraiLogger] 是否已开启 + * + * 除 [MiraiLoggerWithSwitch] 可控制开关外, 其他的所有 [MiraiLogger] 均一直开启. + */ + val isEnabled: Boolean + /** * 随从. 在 this 中调用所有方法后都应继续往 [follower] 传递调用. * [follower] 的存在可以让一次日志被多个日志记录器记录. @@ -151,43 +162,43 @@ interface MiraiLogger { inline fun MiraiLogger.verbose(lazyMessage: () -> String) { - if (this is MiraiLoggerWithSwitch && switch) verbose(lazyMessage()) + if (isEnabled) verbose(lazyMessage()) } inline fun MiraiLogger.verbose(lazyMessage: () -> String, e: Throwable?) { - if (this is MiraiLoggerWithSwitch && switch) verbose(lazyMessage(), e) + if (isEnabled) verbose(lazyMessage(), e) } inline fun MiraiLogger.debug(lazyMessage: () -> String?) { - if (this is MiraiLoggerWithSwitch && switch) debug(lazyMessage()) + if (isEnabled) debug(lazyMessage()) } inline fun MiraiLogger.debug(lazyMessage: () -> String?, e: Throwable?) { - if (this is MiraiLoggerWithSwitch && switch) debug(lazyMessage(), e) + if (isEnabled) debug(lazyMessage(), e) } inline fun MiraiLogger.info(lazyMessage: () -> String?) { - if (this is MiraiLoggerWithSwitch && switch) info(lazyMessage()) + if (isEnabled) info(lazyMessage()) } inline fun MiraiLogger.info(lazyMessage: () -> String?, e: Throwable?) { - if (this is MiraiLoggerWithSwitch && switch) info(lazyMessage(), e) + if (isEnabled) info(lazyMessage(), e) } inline fun MiraiLogger.warning(lazyMessage: () -> String?) { - if (this is MiraiLoggerWithSwitch && switch) warning(lazyMessage()) + if (isEnabled) warning(lazyMessage()) } inline fun MiraiLogger.warning(lazyMessage: () -> String?, e: Throwable?) { - if (this is MiraiLoggerWithSwitch && switch) warning(lazyMessage(), e) + if (isEnabled) warning(lazyMessage(), e) } inline fun MiraiLogger.error(lazyMessage: () -> String?) { - if (this is MiraiLoggerWithSwitch && switch) error(lazyMessage()) + if (isEnabled) error(lazyMessage()) } inline fun MiraiLogger.error(lazyMessage: () -> String?, e: Throwable?) { - if (this is MiraiLoggerWithSwitch && switch) error(lazyMessage(), e) + if (isEnabled) error(lazyMessage(), e) } /** @@ -197,7 +208,8 @@ inline fun MiraiLogger.error(lazyMessage: () -> String?, e: Throwable?) { * * 不应该直接构造这个类的实例. 请使用 [DefaultLogger], 或使用默认的顶层日志记录 [MiraiLogger.Companion] */ -expect open class PlatformLogger @JvmOverloads internal constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase +expect open class PlatformLogger @JvmOverloads constructor(identity: String? = "Mirai") : MiraiLoggerPlatformBase + /** * 不做任何事情的 logger, keep silent. @@ -230,14 +242,19 @@ class SimpleLogger( } companion object { - inline operator fun invoke(crossinline logger: (message: String?, e: Throwable?) -> Unit): SimpleLogger = SimpleLogger(null, logger) + inline operator fun invoke(crossinline logger: (message: String?, e: Throwable?) -> Unit): SimpleLogger = + SimpleLogger(null, logger) - inline operator fun invoke(identity: String?, crossinline logger: (message: String?, e: Throwable?) -> Unit): SimpleLogger = + inline operator fun invoke( + identity: String?, + crossinline logger: (message: String?, e: Throwable?) -> Unit + ): SimpleLogger = SimpleLogger(identity) { _, message, e -> logger(message, e) } - operator fun invoke(logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit): SimpleLogger = SimpleLogger(null, logger) + operator fun invoke(logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit): SimpleLogger = + SimpleLogger(null, logger) } override fun verbose0(message: String?) = logger(LogPriority.VERBOSE, message, null) @@ -259,7 +276,8 @@ class SimpleLogger( * @see disable 关闭 */ @Suppress("MemberVisibilityCanBePrivate") -class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogger, default: Boolean) : MiraiLoggerPlatformBase() { +class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogger, default: Boolean) : + MiraiLoggerPlatformBase() { override val identity: String? get() = delegate.identity /** @@ -268,7 +286,7 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg @PublishedApi internal var switch: Boolean = default - val isEnabled: Boolean get() = switch + override val isEnabled: Boolean get() = switch fun enable() { switch = true @@ -278,16 +296,16 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg switch = false } - override fun verbose0(message: String?) = if (switch) delegate.verbose(message) else Unit - override fun verbose0(message: String?, e: Throwable?) = if (switch) delegate.verbose(message, e) else Unit - override fun debug0(message: String?) = if (switch) delegate.debug(message) else Unit - override fun debug0(message: String?, e: Throwable?) = if (switch) delegate.debug(message, e) else Unit - override fun info0(message: String?) = if (switch) delegate.info(message) else Unit - override fun info0(message: String?, e: Throwable?) = if (switch) delegate.info(message, e) else Unit - override fun warning0(message: String?) = if (switch) delegate.warning(message) else Unit - override fun warning0(message: String?, e: Throwable?) = if (switch) delegate.warning(message, e) else Unit - override fun error0(message: String?) = if (switch) delegate.error(message) else Unit - override fun error0(message: String?, e: Throwable?) = if (switch) delegate.error(message, e) else Unit + override fun verbose0(message: String?) = delegate.verbose(message) + override fun verbose0(message: String?, e: Throwable?) = delegate.verbose(message, e) + override fun debug0(message: String?) = delegate.debug(message) + override fun debug0(message: String?, e: Throwable?) = delegate.debug(message, e) + override fun info0(message: String?) = delegate.info(message) + override fun info0(message: String?, e: Throwable?) = delegate.info(message, e) + override fun warning0(message: String?) = delegate.warning(message) + override fun warning0(message: String?, e: Throwable?) = delegate.warning(message, e) + override fun error0(message: String?) = delegate.error(message) + override fun error0(message: String?, e: Throwable?) = delegate.error(message, e) } /** @@ -298,54 +316,65 @@ class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogg * 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch]. */ abstract class MiraiLoggerPlatformBase : MiraiLogger { + override val isEnabled: Boolean get() = true final override var follower: MiraiLogger? = null final override fun verbose(message: String?) { + if (!isEnabled) return follower?.verbose(message) verbose0(message) } final override fun verbose(message: String?, e: Throwable?) { + if (!isEnabled) return follower?.verbose(message, e) verbose0(message, e) } final override fun debug(message: String?) { + if (!isEnabled) return follower?.debug(message) debug0(message) } final override fun debug(message: String?, e: Throwable?) { + if (!isEnabled) return follower?.debug(message, e) debug0(message, e) } final override fun info(message: String?) { + if (!isEnabled) return follower?.info(message) info0(message) } final override fun info(message: String?, e: Throwable?) { + if (!isEnabled) return follower?.info(message, e) info0(message, e) } final override fun warning(message: String?) { + if (!isEnabled) return follower?.warning(message) warning0(message) } final override fun warning(message: String?, e: Throwable?) { + if (!isEnabled) return follower?.warning(message, e) warning0(message, e) } final override fun error(message: String?) { + if (!isEnabled) return follower?.error(message) error0(message) } final override fun error(message: String?, e: Throwable?) { + if (!isEnabled) return follower?.error(message, e) error0(message, e) } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OverFileSizeMaxException.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OverFileSizeMaxException.kt index b56531ac6..bf6355ac9 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OverFileSizeMaxException.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/OverFileSizeMaxException.kt @@ -17,5 +17,5 @@ import kotlin.jvm.JvmName /** * 图片文件过大 - */ -class OverFileSizeMaxException : IllegalStateException() + */ // 不要删除多平台结构, 这是 kotlin 的 bug +expect class OverFileSizeMaxException() : IllegalStateException \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotataions.kt similarity index 90% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotataions.kt index b14880475..aa6535a7f 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Annotations.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/annotataions.kt @@ -18,7 +18,7 @@ import kotlin.annotation.AnnotationTarget.* * 非常不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.SOURCE) -@Experimental(level = Experimental.Level.ERROR) +@RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target( CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, @@ -36,7 +36,7 @@ annotation class MiraiInternalAPI( * 不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.SOURCE) -@Experimental(level = Experimental.Level.WARNING) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) annotation class MiraiExperimentalAPI( val message: String = "" @@ -49,7 +49,7 @@ annotation class MiraiExperimentalAPI( * 非常不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.SOURCE) -@Experimental(level = Experimental.Level.WARNING) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) annotation class MiraiDebugAPI( val message: String = "" @@ -59,6 +59,6 @@ annotation class MiraiDebugAPI( * 标记一个自 Mirai 某个版本起才支持的 API. */ @Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) -@Retention(AnnotationRetention.BINARY) +@Retention(AnnotationRetention.SOURCE) @MustBeDocumented annotation class SinceMirai(val version: String) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt new file mode 100644 index 000000000..f0495774a --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/channels.kt @@ -0,0 +1,176 @@ +/* + * 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:JvmName("Utils") +@file:JvmMultifileClass + +package net.mamoe.mirai.utils + + +import kotlinx.coroutines.io.ByteReadChannel +import kotlinx.coroutines.io.ByteWriteChannel +import kotlinx.coroutines.io.readAvailable +import kotlinx.io.OutputStream +import kotlinx.serialization.InternalSerializationApi +import kotlinx.io.core.Output +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.io.ByteArrayPool +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName + +// copyTo + +/** + * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] + */ +@OptIn(InternalSerializationApi::class) +suspend fun ByteReadChannel.copyTo(dst: OutputStream) { + @OptIn(MiraiInternalAPI::class) + ByteArrayPool.useInstance { buffer -> + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.write(buffer, 0, size) + } + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] + */ +suspend fun ByteReadChannel.copyTo(dst: Output) { + @OptIn(MiraiInternalAPI::class) + ByteArrayPool.useInstance { buffer -> + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) + } + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] + */ +suspend fun ByteReadChannel.copyTo(dst: ByteWriteChannel) { + @OptIn(MiraiInternalAPI::class) + ByteArrayPool.useInstance { buffer -> + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) + } + } +} + + +/* // 垃圾 kotlin, Unresolved reference: ByteWriteChannel +/** + * 从接收者管道读取所有数据并写入 [dst]. 不会关闭 [dst] + */ +suspend fun ByteReadChannel.copyTo(dst: kotlinx.coroutines.io.ByteWriteChannel) { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.writeFully(it, 0, size) + } while (size != 0) + } +} +*/ + +// copyAndClose + + +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +@OptIn(InternalSerializationApi::class) +suspend fun ByteReadChannel.copyAndClose(dst: OutputStream) { // 在 JVM 这个 API 不是 internal 的 + try { + @OptIn(MiraiInternalAPI::class) + ByteArrayPool.useInstance { buffer -> + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.write(buffer, 0, size) + } + } + } finally { + dst.close() + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +suspend fun ByteReadChannel.copyAndClose(dst: Output) { + try { + @OptIn(MiraiInternalAPI::class) + ByteArrayPool.useInstance { buffer -> + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) + } + } + } finally { + dst.close() + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +suspend fun ByteReadChannel.copyAndClose(dst: ByteWriteChannel) { + @Suppress("DuplicatedCode") + try { + @OptIn(MiraiInternalAPI::class) + ByteArrayPool.useInstance { buffer -> + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) + } + } + } finally { + @Suppress("DuplicatedCode") + dst.close(null) + } +} + +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +suspend fun ByteReadChannel.copyAndClose(dst: io.ktor.utils.io.ByteWriteChannel) { + @Suppress("DuplicatedCode") + try { + @OptIn(MiraiInternalAPI::class) + ByteArrayPool.useInstance { buffer -> + var size: Int + while (this.readAvailable(buffer).also { size = it } > 0) { + dst.writeFully(buffer, 0, size) + } + } + } finally { + @Suppress("DuplicatedCode") + dst.close(null) + } +} + +/*// 垃圾 kotlin, Unresolved reference: ByteWriteChannel +/** + * 从接收者管道读取所有数据并写入 [dst], 最终关闭 [dst] + */ +suspend fun ByteReadChannel.copyAndClose(dst: kotlinx.coroutines.io.ByteWriteChannel) { + dst.close(kotlin.runCatching { + ByteArrayPool.useInstance { + do { + val size = this.readAvailable(it) + dst.writeFully(it, 0, size) + } while (size != 0) + } + }.exceptionOrNull()) +} + + */ \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt new file mode 100644 index 000000000..6aa1922e5 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/contentToString.kt @@ -0,0 +1,172 @@ +/* + * Copyright 2020 Mamoe Technologies and contributors. + * + * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. + * Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link. + * + * https://github.com/mamoe/mirai/blob/master/LICENSE + */ + +@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH") + +package net.mamoe.mirai.utils + +import net.mamoe.mirai.utils.io.toUHexString +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 + +private val indent: String = " ".repeat(4) + +/** + * 将所有元素加入转换为多行的字符串表示. + */ +@MiraiDebugAPI +internal fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { + return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform) +} + +/** + * 将内容格式化为较可读的字符串输出. + * + * 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. e.g. `0x1000(4096)` + * [ByteArray] 和 [UByteArray]: 十六进制表示, 通过 [ByteArray.toUHexString] + * [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString. + * [Map]: 多行输出. 每行显示一个值. 递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 + * `data class`: 调用其 [toString] + * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 + */ +@Suppress("FunctionName") // 这样就不容易被 IDE 提示 +@MiraiDebugAPI("Extremely slow") +//@Suppress("Unsupported") // false positive +fun Any?._miraiContentToString(prefix: String = ""): String = when (this) { + is Unit -> "Unit" + is UInt -> "0x" + this.toUHexString("") + "($this)" + is UByte -> "0x" + this.toUHexString() + "($this)" + is UShort -> "0x" + this.toUHexString("") + "($this)" + is ULong -> "0x" + this.toUHexString("") + "($this)" + is Int -> "0x" + this.toUHexString("") + "($this)" + is Byte -> "0x" + this.toUHexString() + "($this)" + is Short -> "0x" + this.toUHexString("") + "($this)" + is Long -> "0x" + this.toUHexString("") + "($this)" + + is Boolean -> if (this) "true" else "false" + + is ByteArray -> { + if (this.size == 0) "<Empty ByteArray>" + else this.toUHexString() + } + is UByteArray -> { + if (this.size == 0) "<Empty UByteArray>" + else this.toUHexString() + } + is ShortArray -> { + if (this.size == 0) "<Empty ShortArray>" + else this.iterator()._miraiContentToString() + } + is IntArray -> { + if (this.size == 0) "<Empty IntArray>" + else this.iterator()._miraiContentToString() + } + is LongArray -> { + if (this.size == 0) "<Empty LongArray>" + else this.iterator()._miraiContentToString() + } + is FloatArray -> { + if (this.size == 0) "<Empty FloatArray>" + else this.iterator()._miraiContentToString() + } + is DoubleArray -> { + if (this.size == 0) "<Empty DoubleArray>" + else this.iterator()._miraiContentToString() + } + is UShortArray -> { + if (this.size == 0) "<Empty ShortArray>" + else this.iterator()._miraiContentToString() + } + is UIntArray -> { + if (this.size == 0) "<Empty IntArray>" + else this.iterator()._miraiContentToString() + } + is ULongArray -> { + if (this.size == 0) "<Empty LongArray>" + else this.iterator()._miraiContentToString() + } + is Array<*> -> { + if (this.size == 0) "<Empty Array>" + else this.iterator()._miraiContentToString() + } + is BooleanArray -> { + if (this.size == 0) "<Empty BooleanArray>" + else this.iterator()._miraiContentToString() + } + + is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } + is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } + is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } + is Map<*, *> -> this.entries.joinToString( + prefix = "{", + postfix = "}" + ) { it.key._miraiContentToString(prefix) + "=" + it.value._miraiContentToString(prefix) } + else -> { + if (this == null) "null" + else if (this::class.isData) this.toString() + else { + if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) { + this.contentToStringReflectively(prefix + indent) + } else this.toString() + /* + (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + "{\n" + + this::class.members.asSequence().filterIsInstance<KProperty<*>>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC } + .joinToStringPrefixed( + prefix = indent + ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(indent) }.getOrElse { "<!>" } } + */ + } + } +} + +internal expect fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? + +@MiraiDebugAPI +private fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)? = null): String { + val newPrefix = "$prefix " + return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" + + this.allMembersFromSuperClassesMatching { it.qualifiedName?.startsWith("net.mamoe.mirai") == true } + .distinctBy { it.name } + .filterNot { it.name.contains("$") || it.name == "Companion" || it.isConst || it.name == "serialVersionUID" } + .mapNotNull { + val value = it.getValueAgainstPermission(this) ?: return@mapNotNull null + if (filter != null) { + if (!filter(it.name, value)) + return@mapNotNull it.name to value + else { + return@mapNotNull null + } + } + it.name to value + } + .joinToStringPrefixed( + prefix = newPrefix + ) { (name: String, value: Any?) -> + "$name=" + kotlin.runCatching { + if (value == this) "<this>" + else value._miraiContentToString(newPrefix) + }.getOrElse { "<!>" } + } + "\n$prefix}" +} + +private fun KClass<out Any>.thisClassAndSuperclassSequence(): Sequence<KClass<out Any>> { + return sequenceOf(this) + + this.supertypes.asSequence() + .mapNotNull { type -> type.classifier?.takeIf { it is KClass<*> }?.takeIf { it != Any::class } as? KClass<out Any> }.flatMap { it.thisClassAndSuperclassSequence() } +} + +@Suppress("UNCHECKED_CAST") +private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any>) -> Boolean): Sequence<KProperty1<Any, *>> { + return this::class.thisClassAndSuperclassSequence() + .filter { classFilter(it) } + .map { it.members } + .flatMap { it.asSequence() } + .filterIsInstance<KProperty1<*, *>>() + .mapNotNull { it as KProperty1<Any, *> } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt index 36ac8186e..0b93f83b5 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/ECDH.kt @@ -19,7 +19,9 @@ expect interface ECDHPublicKey { fun getEncoded(): ByteArray } -expect class ECDHKeyPair { +internal expect class ECDHKeyPairImpl : ECDHKeyPair + +interface ECDHKeyPair { val privateKey: ECDHPrivateKey val publicKey: ECDHPublicKey @@ -27,6 +29,15 @@ expect class ECDHKeyPair { * 私匙和固定公匙([initialPublicKey]) 计算得到的 shareKey */ val initialShareKey: ByteArray + + object DefaultStub : ECDHKeyPair { + val defaultPublicKey = "020b03cf3d99541f29ffec281bebbd4ea211292ac1f53d7128".chunkedHexToBytes() + val defaultShareKey = "4da0f614fc9f29c2054c77048a6566d7".chunkedHexToBytes() + + override val privateKey: Nothing get() = error("stub!") + override val publicKey: Nothing get() = error("stub!") + override val initialShareKey: ByteArray get() = defaultShareKey + } } /** @@ -41,6 +52,8 @@ expect class ECDH(keyPair: ECDHKeyPair) { fun calculateShareKeyByPeerPublicKey(peerPublicKey: ECDHPublicKey): ByteArray companion object { + val isECDHAvailable: Boolean + /** * 由完整的 publicKey ByteArray 得到 [ECDHPublicKey] */ @@ -60,14 +73,11 @@ expect class ECDH(keyPair: ECDHKeyPair) { override fun toString(): String } -/** - * - */ @Suppress("FunctionName") expect fun ECDH(): ECDH -val initialPublicKey = - ECDH.constructPublicKey("3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8".chunkedHexToBytes()) +val initialPublicKey + get() = ECDH.constructPublicKey("3046301006072A8648CE3D020106052B8104001F03320004928D8850673088B343264E0C6BACB8496D697799F37211DEB25BB73906CB089FEA9639B4E0260498B51A992D50813DA8".chunkedHexToBytes()) private val commonHeadFor02 = "302E301006072A8648CE3D020106052B8104001F031A00".chunkedHexToBytes() private val commonHeadForNot02 = "3046301006072A8648CE3D020106052B8104001F033200".chunkedHexToBytes() private const val constantHead = "3046301006072A8648CE3D020106052B8104001F03320004" diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/Proto.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/Proto.kt deleted file mode 100644 index 1096a3d6d..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/Proto.kt +++ /dev/null @@ -1,293 +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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "NO_REFLECTION_IN_CLASS_PATH") - -package net.mamoe.mirai.utils.cryptor - -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.readBytes -import kotlinx.io.core.readUInt -import kotlinx.io.core.readULong -import net.mamoe.mirai.utils.MiraiDebugAPI -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.* -import kotlin.jvm.JvmStatic - -// ProtoBuf utilities - - -@Suppress("FunctionName", "SpellCheckingInspection") -/* - * Type Meaning Used For - * 0 Varint int32, int64, uint32, uint64, sint32, sint64, bool, enum - * 1 64-bit fixed64, sfixed64, double - * 2 Length-delimi string, bytes, embedded messages, packed repeated fields - * 3 Start group Groups (deprecated) - * 4 End group Groups (deprecated) - * 5 32-bit fixed32, sfixed32, float - * - * https://www.jianshu.com/p/f888907adaeb - */ -@MiraiDebugAPI -fun ProtoFieldId(serializedId: UInt): ProtoFieldId = - ProtoFieldId( - protoFieldNumber(serializedId), - protoType(serializedId) - ) - -@MiraiDebugAPI -data class ProtoFieldId( - val fieldNumber: Int, - val type: ProtoType -) { - override fun toString(): String = "$type $fieldNumber" -} - -@Suppress("SpellCheckingInspection") -@MiraiDebugAPI -enum class ProtoType(val value: Byte, private val typeName: String) { - /** - * int32, int64, uint32, uint64, sint32, sint64, bool, enum - */ - VAR_INT(0x00, "varint"), - - /** - * fixed64, sfixed64, double - */ - BIT_64(0x01, " 64bit"), - - /** - * string, bytes, embedded messages, packed repeated fields - */ - LENGTH_DELIMI(0x02, "delimi"), - - /** - * Groups (deprecated) - */ - START_GROUP(0x03, "startg"), - - /** - * Groups (deprecated) - */ - END_GROUP(0x04, " endg"), - - /** - * fixed32, sfixed32, float - */ - BIT_32(0x05, " 32bit"), - ; - - override fun toString(): String = this.typeName - - companion object { - fun valueOf(value: Byte): ProtoType = values().firstOrNull { it.value == value } ?: error("Unknown ProtoType $value") - } -} - -/** - * 由 ProtoBuf 序列化后的 id 得到类型 - * - * serializedId = (fieldNumber << 3) | wireType - */ -@MiraiDebugAPI -fun protoType(number: UInt): ProtoType = - ProtoType.valueOf(number.toInt().shl(29).ushr(29).toByte()) - -/** - * ProtoBuf 序列化后的 id 转为序列前标记的 id - * - * serializedId = (fieldNumber << 3) | wireType - */ -@MiraiDebugAPI -fun protoFieldNumber(number: UInt): Int = number.toInt().ushr(3) - -@MiraiDebugAPI -class ProtoMap(map: MutableMap<ProtoFieldId, Any>) : MutableMap<ProtoFieldId, Any> by map { - companion object { - @JvmStatic - internal val indent: String = " " - } - - override fun toString(): String { - return this.entries.joinToString(prefix = "ProtoMap(size=$size){\n$indent", postfix = "\n}", separator = "\n$indent") { - "${it.key}=" + it.value.contentToString() - } - } - - fun toStringPrefixed(prefix: String): String { - return this.entries.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent") { - "${it.key}=" + it.value.contentToString(prefix) - } - } - /* - override fun put(key: ProtoFieldId, value: Any): Any? { - println("${key}=" + value.contentToString()) - return null - }*/ -} - -/** - * 将所有元素加入转换为多行的字符串表示. - */ -@MiraiDebugAPI -fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { - return this.joinToString(prefix = "$prefix${ProtoMap.indent}", separator = "\n$prefix${ProtoMap.indent}", transform = transform) -} - -/** - * 将内容格式化为较可读的字符串输出. - * - * 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. e.g. `0x1000(4096)` - * [ByteArray] 和 [UByteaArray]: 十六进制表示, 通过 [ByteArray.toUHexString] - * [ProtoMap]: 调用 [ProtoMap.toStringPrefixed] - * [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString. - * [Map]: 多行输出. 每行显示一个值. 递归调用 [contentToString]. 嵌套结构将会以缩进表示 - * `data class`: 调用其 [toString] - * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [contentToString]. 嵌套结构将会以缩进表示 - */ -@MiraiDebugAPI("Extremely slow") -//@Suppress("Unsupported") // false positive -fun Any?.contentToString(prefix: String = ""): String = when (this) { - is Unit -> "Unit" - is UInt -> "0x" + this.toUHexString("") + "($this)" - is UByte -> "0x" + this.toUHexString() + "($this)" - is UShort -> "0x" + this.toUHexString("") + "($this)" - is ULong -> "0x" + this.toUHexString("") + "($this)" - is Int -> "0x" + this.toUHexString("") + "($this)" - is Byte -> "0x" + this.toUHexString() + "($this)" - is Short -> "0x" + this.toUHexString("") + "($this)" - is Long -> "0x" + this.toUHexString("") + "($this)" - - is UVarInt -> "0x" + this.toUHexString("") + "($this)" - - is Boolean -> if (this) "true" else "false" - - is ByteArray -> { - if (this.size == 0) "<Empty ByteArray>" - else this.toUHexString() - } - is UByteArray -> { - if (this.size == 0) "<Empty UByteArray>" - else this.toUHexString() - } - is ShortArray -> { - if (this.size == 0) "<Empty ShortArray>" - else this.iterator().contentToString() - } - is IntArray -> { - if (this.size == 0) "<Empty IntArray>" - else this.iterator().contentToString() - } - is LongArray -> { - if (this.size == 0) "<Empty LongArray>" - else this.iterator().contentToString() - } - is FloatArray -> { - if (this.size == 0) "<Empty FloatArray>" - else this.iterator().contentToString() - } - is DoubleArray -> { - if (this.size == 0) "<Empty DoubleArray>" - else this.iterator().contentToString() - } - is UShortArray -> { - if (this.size == 0) "<Empty ShortArray>" - else this.iterator().contentToString() - } - is UIntArray -> { - if (this.size == 0) "<Empty IntArray>" - else this.iterator().contentToString() - } - is ULongArray -> { - if (this.size == 0) "<Empty LongArray>" - else this.iterator().contentToString() - } - is Array<*> -> { - if (this.size == 0) "<Empty Array>" - else this.iterator().contentToString() - } - is BooleanArray -> { - if (this.size == 0) "<Empty BooleanArray>" - else this.iterator().contentToString() - } - - is ProtoMap -> "ProtoMap(size=$size){\n" + this.toStringPrefixed("$prefix${ProtoMap.indent}${ProtoMap.indent}") + "\n$prefix${ProtoMap.indent}}" - is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) } - is Iterator<*> -> this.asSequence().joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) } - is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it.contentToString(prefix) } - is Map<*, *> -> this.entries.joinToString(prefix = "{", postfix = "}") { it.key.contentToString(prefix) + "=" + it.value.contentToString(prefix) } - else -> { - if (this == null) "null" - else if (this::class.isData) this.toString() - else { - if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) { - this.contentToStringReflectively(prefix + ProtoMap.indent) - } else this.toString() - /* - (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + "{\n" + - this::class.members.asSequence().filterIsInstance<KProperty<*>>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC } - .joinToStringPrefixed( - prefix = ProtoMap.indent - ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(ProtoMap.indent) }.getOrElse { "<!>" } } - */ - } - } -} - -@MiraiExperimentalAPI("Extremely slow") -@MiraiDebugAPI("Extremely slow") -expect fun Any.contentToStringReflectively(prefix: String = "", filter: ((String, Any?) -> Boolean)? = null): String - -@MiraiDebugAPI -@Suppress("UNCHECKED_CAST") -fun ByteReadPacket.readProtoMap(length: Long = this.remaining): ProtoMap { - val map = ProtoMap(mutableMapOf()) - - - val expectingRemaining = this.remaining - length - while (this.remaining != expectingRemaining) { - require(this.remaining > expectingRemaining) { "Expecting to read $length bytes, but read ${expectingRemaining + length - this.remaining}" } - - try { - val id = ProtoFieldId(readUVarInt()) - - fun readValue(): Any = when (id.type) { - ProtoType.VAR_INT -> UVarInt(readUVarInt()) - ProtoType.BIT_32 -> readUInt() - ProtoType.BIT_64 -> readULong() - ProtoType.LENGTH_DELIMI -> tryReadProtoMapOrByteArray(readUVarInt().toInt()) - - ProtoType.START_GROUP -> Unit - ProtoType.END_GROUP -> Unit - } - - if (map.containsKey(id)) { - if (map[id] !is MutableList<*>) map[id] = mutableListOf(map[id]!!) - (map[id] as MutableList<Any>) += readValue() - } else { - map[id] = readValue() - } - } catch (e: IllegalStateException) { - e.logStacktrace() - return map - } - } - return map -} - -private fun ByteReadPacket.tryReadProtoMapOrByteArray(length: Int): Any { - val bytes = this.readBytes(length) - return try { - bytes.toReadPacket().readProtoMap().apply { require(none { it.key.type == ProtoType.START_GROUP || it.key.type == ProtoType.END_GROUP }) } - } catch (e: Exception) { - bytes - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt index ab25f8002..3e43b3fde 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/cryptor/TEA.kt @@ -10,8 +10,8 @@ package net.mamoe.mirai.utils.cryptor import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.IoBuffer import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.io.ByteArrayPool import net.mamoe.mirai.utils.io.toByteArray import net.mamoe.mirai.utils.io.toUHexString @@ -20,7 +20,6 @@ import kotlin.experimental.xor import kotlin.jvm.JvmStatic import kotlin.random.Random - /** * 解密错误 */ @@ -29,98 +28,52 @@ class DecryptionFailedException : Exception { constructor(message: String?) : super(message) } - -// region encrypt - /** - * 使用 [key] 解密 [this] + * TEA 算法加密解密工具类. * - * @param key 长度至少为 16 - * @throws DecryptionFailedException 解密错误时 + * **注意**: 此为 Mirai 内部 API. 它可能会在任何时刻被改变. */ -fun ByteArray.encryptBy(key: ByteArray, length: Int = this.size): ByteArray = - TEA.encrypt(this, key, sourceLength = length) +@MiraiInternalAPI +object TEA { + // TODO: 2020/2/28 使用 stream 式输入以避免缓存 -/** - * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密. - * - * @param key 长度至少为 16 - * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程 - * @throws DecryptionFailedException 解密错误时 - */ -inline fun ByteReadPacket.encryptBy(key: ByteArray, offset: Int = 0, length: Int = remaining.toInt() - offset, consumer: (ByteArray) -> Unit) { - ByteArrayPool.useInstance { - this.readFully(it, offset, length) - consumer(it.encryptBy(key, length = length)) + /** + * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密. + * + * @param key 长度至少为 16 + * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程 + * @throws DecryptionFailedException 解密错误时 + */ + inline fun encrypt( + receiver: ByteReadPacket, + key: ByteArray, + offset: Int = 0, + length: Int = receiver.remaining.toInt() - offset, + consumer: (ByteArray) -> Unit + ) { + ByteArrayPool.useInstance { + receiver.readFully(it, offset, length) + consumer(encrypt(it, key, length = length)) + } } -} -// endregion + @JvmStatic + fun decrypt(receiver: ByteReadPacket, key: ByteArray, offset: Int = 0, length: Int = (receiver.remaining - offset).toInt()): ByteReadPacket = + decryptAsByteArray(receiver, key, offset, length) { data -> ByteReadPacket(data) } - -// region decrypt - -/** - * 使用 [key] 解密 [this]. - * - * @param key 固定长度 16 - * @throws DecryptionFailedException 解密错误时 - */ -fun ByteArray.decryptBy(key: ByteArray, length: Int = this.size): ByteArray = - TEA.decrypt(checkDataLengthAndReturnSelf(length), key, sourceLength = length) - -/** - * 使用 [key] 解密 [this]. - * [key] 将会被读取掉前 16 个字节 - * 将会使用 [ByteArrayPool] 来缓存 [key]. - * - * @param key 长度至少为 16 - * @throws DecryptionFailedException 解密错误时 - */ -fun ByteArray.decryptBy(key: IoBuffer, length: Int = this.size): ByteArray { - checkDataLengthAndReturnSelf(length) - return ByteArrayPool.useInstance { keyBuffer -> - key.readFully(keyBuffer, 0, key.readRemaining) - TEA.decrypt(this, keyBuffer, sourceLength = length) + inline fun <R> decryptAsByteArray( + receiver: ByteReadPacket, + key: ByteArray, + offset: Int = 0, + length: Int = (receiver.remaining - offset).toInt(), + consumer: (ByteArray) -> R + ): R { + return ByteArrayPool.useInstance { + receiver.readFully(it, offset, length) + consumer(decrypt(it, key, length)) + }.also { receiver.close() } } -} -/** - * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 解密. - * - * @param key 长度至少为 16 - * @throws DecryptionFailedException 解密错误时 - */ -fun IoBuffer.decryptBy(key: ByteArray, offset: Int = 0, length: Int = readRemaining - offset): ByteArray { - return ByteArrayPool.useInstance { - this.readFully(it, offset, length) - it.checkDataLengthAndReturnSelf(length) - TEA.decrypt(it, key, length) - } -} - -// endregion - -// region ByteReadPacket extension - -fun ByteReadPacket.decryptBy(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) } - -fun ByteReadPacket.decryptBy(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt()): ByteReadPacket = decryptAsByteArray(key, offset, length) { data -> ByteReadPacket(data) } - -inline fun <R> ByteReadPacket.decryptAsByteArray(key: ByteArray, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R = - ByteArrayPool.useInstance { - readFully(it, offset, length) - consumer(it.decryptBy(key, length)) - }.also { close() } - -inline fun <R> ByteReadPacket.decryptAsByteArray(key: IoBuffer, offset: Int = 0, length: Int = (this.remaining - offset).toInt(), consumer: (ByteArray) -> R): R = - ByteArrayPool.useInstance { - readFully(it, offset, length) - consumer(it.decryptBy(key, length)) - }.also { close() } - -// endregion -private object TEA { private const val UINT32_MASK = 0xffffffffL private fun doOption(data: ByteArray, key: ByteArray, length: Int, encrypt: Boolean): ByteArray { @@ -345,15 +298,25 @@ private object TEA { private fun fail(): Nothing = throw DecryptionFailedException() - @PublishedApi + /** + * 使用 [key] 加密 [source] + * + * @param key 长度至少为 16 + * @throws DecryptionFailedException 解密错误时 + */ @JvmStatic - internal fun encrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray = - doOption(source, key, sourceLength, true) + fun encrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray = + doOption(source, key, length, true) - @PublishedApi + /** + * 使用 [key] 解密 [source] + * + * @param key 长度至少为 16 + * @throws DecryptionFailedException 解密错误时 + */ @JvmStatic - internal fun decrypt(source: ByteArray, key: ByteArray, sourceLength: Int = source.size): ByteArray = - doOption(source, key, sourceLength, false) + fun decrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray = + doOption(source, key, length, false) private fun ByteArray.pack(offset: Int, len: Int): Long { var result: Long = 0 diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt index cb7dac2e7..12d327afa 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayPool.kt @@ -7,19 +7,36 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:Suppress("MemberVisibilityCanBePrivate") + package net.mamoe.mirai.utils.io import kotlinx.io.pool.DefaultPool import kotlinx.io.pool.ObjectPool +import net.mamoe.mirai.utils.MiraiInternalAPI -internal const val DEFAULT_BYTE_ARRAY_POOL_SIZE = 256 -internal const val DEFAULT_BYTE_ARRAY_SIZE = 81920 +/** + * 缓存 [ByteArray] 实例的 [ObjectPool] + * + * **注意**: 这是 Mirai 内部 API. 它可能在任何时刻被改动. 不要将它用于生产环境 + */ +@MiraiInternalAPI +object ByteArrayPool : DefaultPool<ByteArray>(256) { + /** + * 每一个 [ByteArray] 的大小 + */ + const val BUFFER_SIZE: Int = 81920 / 2 -val ByteArrayPool: ObjectPool<ByteArray> = ByteArrayPoolImpl - -private object ByteArrayPoolImpl : DefaultPool<ByteArray>(DEFAULT_BYTE_ARRAY_POOL_SIZE) { - override fun produceInstance(): ByteArray = ByteArray(DEFAULT_BYTE_ARRAY_SIZE) + override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE) override fun clearInstance(instance: ByteArray): ByteArray = instance + + fun checkBufferSize(size: Int) { + require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } + } + + fun checkBufferSize(size: Long) { + require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } + } } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt deleted file mode 100644 index ed1abaa03..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/DebugUtil.kt +++ /dev/null @@ -1,102 +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 - */ - -@file:Suppress("NOTHING_TO_INLINE") -@file:JvmMultifileClass -@file:JvmName("Utils") - -package net.mamoe.mirai.utils.io - -import kotlinx.io.core.* -import kotlinx.io.pool.useInstance -import net.mamoe.mirai.utils.DefaultLogger -import net.mamoe.mirai.utils.MiraiDebugAPI -import net.mamoe.mirai.utils.MiraiLoggerWithSwitch -import net.mamoe.mirai.utils.withSwitch -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName - - -@MiraiDebugAPI("Unsatble") -val DebugLogger : MiraiLoggerWithSwitch = DefaultLogger("Packet Debug").withSwitch(false) - -@MiraiDebugAPI("Unstable") -inline fun Throwable.logStacktrace(message: String? = null) = DebugLogger.error(message, this) - -@MiraiDebugAPI("Low efficiency.") -inline fun String.debugPrintThis(name: String): String { - DebugLogger.debug("$name=$this") - return this -} - -@MiraiDebugAPI("Low efficiency.") -inline fun ByteArray.debugPrintThis(name: String): ByteArray { - DebugLogger.debug(name + "=" + this.toUHexString()) - return this -} - -@MiraiDebugAPI("Low efficiency.") -inline fun IoBuffer.debugPrintThis(name: String): IoBuffer { - ByteArrayPool.useInstance { - val count = this.readAvailable(it) - DebugLogger.debug(name + "=" + it.toUHexString(offset = 0, length = count)) - return it.toIoBuffer(0, count) - } -} - -@MiraiDebugAPI("Low efficiency.") -inline fun IoBuffer.debugCopyUse(block: IoBuffer.() -> Unit): IoBuffer { - ByteArrayPool.useInstance { - val count = this.readAvailable(it) - block(it.toIoBuffer(0, count)) - return it.toIoBuffer(0, count) - } -} - -@MiraiDebugAPI("Low efficiency.") -inline fun Input.debugDiscardExact(n: Number, name: String = "") { - DebugLogger.debug("Discarded($n) $name=" + this.readBytes(n.toInt()).toUHexString()) -} - -@MiraiDebugAPI("Low efficiency.") -inline fun ByteReadPacket.debugPrintThis(name: String = ""): ByteReadPacket { - ByteArrayPool.useInstance { - val count = this.readAvailable(it) - DebugLogger.debug("ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count)) - return it.toReadPacket(0, count) - } -} - -/** - * 备份数据, 并在 [block] 失败后执行 [onFail]. - * - * 此方法非常低效. 请仅在测试环境使用. - */ -@MiraiDebugAPI("Low efficiency") -@UseExperimental(ExperimentalContracts::class) -inline fun <R> Input.debugIfFail(name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R): R { - - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - callsInPlace(onFail, InvocationKind.UNKNOWN) - } - ByteArrayPool.useInstance { - val count = this.readAvailable(it) - try { - return it.toReadPacket(0, count).use(block) - } catch (e: Throwable) { - onFail(it.take(count).toByteArray()).readAvailable(it) - DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count)) - throw e - } - } -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt index c19705eae..9e4b98f1a 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/PlatformDatagramChannel.kt @@ -11,7 +11,6 @@ package net.mamoe.mirai.utils.io import kotlinx.io.core.ByteReadPacket import kotlinx.io.core.Closeable -import kotlinx.io.errors.IOException import net.mamoe.mirai.utils.MiraiInternalAPI /** @@ -32,11 +31,6 @@ expect class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Cl val isOpen: Boolean } -/** - * Channel 被关闭 - */ -expect class ClosedChannelException : IOException - /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/Varint.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/Varint.kt deleted file mode 100644 index 75c5d9941..000000000 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/Varint.kt +++ /dev/null @@ -1,150 +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 - */ - -@file:JvmName("Varint") -@file:Suppress("EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.utils.io - -import kotlinx.io.core.Input -import kotlinx.io.core.Output -import kotlin.experimental.or -import kotlin.jvm.JvmName -import kotlin.jvm.JvmSynthetic - -/** - * Tool class for VarInt or VarLong operations. - * - * Some code from http://wiki.vg/Protocol. - * - * Source project: [Nukkit](http://github.com/nukkit/nukkit) - * - * @author MagicDroidX from Nukkit Project - * @author lmlstarqaq from Nukkit Project - */ - -internal fun encodeZigZag32(signedInt: Int): Long { - return (signedInt shl 1 xor (signedInt shr 31)).toLong() -} - -@JvmSynthetic -internal fun decodeZigZag32(uint: UInt): Int { - return decodeZigZag32(uint.toLong()) -} - -internal fun decodeZigZag32(uint: Long): Int { - return (uint shr 1).toInt() xor -(uint and 1).toInt() -} - -internal fun encodeZigZag64(signedLong: Long): Long { - return signedLong shl 1 xor (signedLong shr 63) -} - -internal fun decodeZigZag64(signedLong: Long): Long { - return signedLong.ushr(1) xor -(signedLong and 1) -} - - -inline class UVarInt( - val data: UInt -) - -@JvmSynthetic -fun Input.readUVarInt(): UInt { - return read(this, 5).toUInt() -} - - -fun Input.readVarLong(): Long { - return decodeZigZag64(readUVarLong().toLong()) -} - - -@JvmSynthetic -fun Input.readUVarLong(): ULong { - return read(this, 10).toULong() -} - -fun Output.writeVarInt(signedInt: Int) { - this.writeUVarInt(encodeZigZag32(signedInt)) -} - -@JvmSynthetic -fun Output.writeUVarInt(uint: UInt) { - return writeUVarInt(uint.toLong()) -} - -fun Output.writeUVarInt(uint: Long) { - this.write0(uint) -} - -fun Output.writeVarLong(signedLong: Long) { - this.writeUVarLong(encodeZigZag64(signedLong)) -} - -fun Output.writeUVarLong(ulong: Long) { - this.write0(ulong) -} - -fun UVarInt.toByteArray(): ByteArray { - val list = mutableListOf<Byte>() - var value = this.data.toLong() - do { - var temp = (value and 127).toByte() - value = value ushr 7 - if (value != 0L) { - temp = temp or 128.toByte() - } - list += temp - } while (value != 0L) - return list.toByteArray() -} - -fun UVarInt.toUHexString(separator: String = " "): String = buildString { - var value = data.toLong() - - var isFirst = true - do { - if (!isFirst) { - append(separator) - } - var temp = (value and 127).toByte() - value = value ushr 7 - if (value != 0L) { - temp = temp or 128.toByte() - } - append(temp.toUByte().fixToUHex()) - isFirst = false - } while (value != 0L) -} - -private fun Output.write0(long: Long) { - var value = long - do { - var temp = (value and 127).toByte() - value = value ushr 7 - if (value != 0L) { - temp = temp or 128.toByte() - } - this.writeByte(temp) - } while (value != 0L) -} - -private fun read(stream: Input, maxSize: Int): Long { - var value: Long = 0 - var size = 0 - var b = stream.readByte().toInt() - while (b and 0x80 == 0x80) { - value = value or ((b and 0x7F).toLong() shl size++ * 7) - require(size < maxSize) { "VarLong too big(expecting maxSize=$maxSize)" } - b = stream.readByte().toInt() - } - - return value or ((b and 0x7F).toLong() shl size * 7) -} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/byteArrays.kt similarity index 96% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/byteArrays.kt index 6a191ce29..54cfe4507 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/ByteArrayUtil.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/byteArrays.kt @@ -30,7 +30,7 @@ import kotlin.jvm.JvmSynthetic @JvmOverloads @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray -@UseExperimental(ExperimentalUnsignedTypes::class) +@OptIn(ExperimentalUnsignedTypes::class) fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { require(offset >= 0) { "offset shouldn't be negative: $offset" } require(length >= 0) { "length shouldn't be negative: $length" } @@ -54,7 +54,7 @@ fun List<Byte>.toUHexString(separator: String = " ", offset: Int = 0, length: In @JvmOverloads @Suppress("DuplicatedCode") // false positive. foreach is not common to UByteArray and ByteArray -@UseExperimental(ExperimentalUnsignedTypes::class) +@OptIn(ExperimentalUnsignedTypes::class) fun ByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { this.checkOffsetAndLength(offset, length) if (length == 0) { @@ -99,7 +99,7 @@ inline fun ByteArray.encodeToString(charset: Charset = Charsets.UTF_8): String = inline fun ByteArray.toReadPacket(offset: Int = 0, length: Int = this.size - offset) = ByteReadPacket(this, offset = offset, length = length) -@UseExperimental(ExperimentalContracts::class) +@OptIn(ExperimentalContracts::class) inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R { contract { callsInPlace(t, InvocationKind.EXACTLY_ONCE) diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt new file mode 100644 index 000000000..278391688 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/chunked.kt @@ -0,0 +1,144 @@ +/* + * 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.utils.io + +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.io.ByteReadChannel +import kotlinx.io.InputStream +import kotlinx.io.core.ByteReadPacket +import kotlinx.io.core.Input +import kotlinx.io.pool.useInstance +import net.mamoe.mirai.utils.MiraiInternalAPI +import kotlinx.serialization.InternalSerializationApi + + +/** + * 由 [chunkedFlow] 分割得到的区块 + */ +class ChunkedInput( + /** + * 区块的数据. + * 由 [ByteArrayPool] 缓存并管理, 只可在 [Flow.collect] 中访问. + * 它的大小由 [ByteArrayPool.BUFFER_SIZE] 决定, 而有效(有数据)的大小由 [bufferSize] 决定. + * + * **注意**: 不要将他带出 [Flow.collect] 作用域, 否则将造成内存泄露 + */ + val buffer: ByteArray, + internal var size: Int +) { + /** + * [buffer] 的有效大小 + */ + val bufferSize: Int get() = size +} + +/** + * 创建将 [ByteReadPacket] 以固定大小分割的 [Sequence]. + * + * 对于一个 1000 长度的 [ByteReadPacket] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence], + * 其长度分别为: 300, 300, 300, 100. + * + * 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence] + */ +@OptIn(MiraiInternalAPI::class) +fun ByteReadPacket.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> { + ByteArrayPool.checkBufferSize(sizePerPacket) + if (this.remaining <= sizePerPacket.toLong()) { + ByteArrayPool.useInstance { buffer -> + return flowOf(ChunkedInput(buffer, this.readAvailable(buffer, 0, sizePerPacket))) + } + } + return flow { + ByteArrayPool.useInstance { buffer -> + val chunkedInput = ChunkedInput(buffer, 0) + do { + chunkedInput.size = this@chunkedFlow.readAvailable(buffer, 0, sizePerPacket) + emit(chunkedInput) + } while (this@chunkedFlow.isNotEmpty) + } + } +} + +/** + * 创建将 [ByteReadChannel] 以固定大小分割的 [Sequence]. + * + * 对于一个 1000 长度的 [ByteReadChannel] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence], + * 其长度分别为: 300, 300, 300, 100. + */ +@OptIn(MiraiInternalAPI::class) +fun ByteReadChannel.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> { + ByteArrayPool.checkBufferSize(sizePerPacket) + if (this.isClosedForRead) { + return flowOf() + } + return flow { + ByteArrayPool.useInstance { buffer -> + val chunkedInput = ChunkedInput(buffer, 0) + do { + chunkedInput.size = this@chunkedFlow.readAvailable(buffer, 0, sizePerPacket) + emit(chunkedInput) + } while (!this@chunkedFlow.isClosedForRead) + } + } +} + + +/** + * 创建将 [Input] 以固定大小分割的 [Sequence]. + * + * 对于一个 1000 长度的 [Input] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence], + * 其长度分别为: 300, 300, 300, 100. + */ +@OptIn(MiraiInternalAPI::class, ExperimentalCoroutinesApi::class) +internal fun Input.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> { + ByteArrayPool.checkBufferSize(sizePerPacket) + + if (this.endOfInput) { + return flowOf() + } + + return flow { + ByteArrayPool.useInstance { buffer -> + val chunkedInput = ChunkedInput(buffer, 0) + while (!this@chunkedFlow.endOfInput) { + chunkedInput.size = this@chunkedFlow.readAvailable(buffer, 0, sizePerPacket) + emit(chunkedInput) + } + } + } +} + +/** + * 创建将 [ByteReadPacket] 以固定大小分割的 [Sequence]. + * + * 对于一个 1000 长度的 [ByteReadPacket] 和参数 [sizePerPacket] = 300, 将会产生含四个元素的 [Sequence], + * 其长度分别为: 300, 300, 300, 100. + * + * 若 [ByteReadPacket.remaining] 小于 [sizePerPacket], 将会返回唯一元素 [this] 的 [Sequence] + */ +@OptIn( + MiraiInternalAPI::class, ExperimentalCoroutinesApi::class, InternalSerializationApi::class +) +internal fun InputStream.chunkedFlow(sizePerPacket: Int): Flow<ChunkedInput> { + ByteArrayPool.checkBufferSize(sizePerPacket) + + return flow { + ByteArrayPool.useInstance { buffer -> + val chunkedInput = ChunkedInput(buffer, 0) + while (this@chunkedFlow.available() != 0) { + chunkedInput.size = this@chunkedFlow.read(buffer, 0, sizePerPacket) + emit(chunkedInput) + } + } + } +} \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt similarity index 87% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt index 75a93a88e..c47f22772 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/TypeConversion.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/conversion.kt @@ -21,14 +21,24 @@ import kotlin.random.nextInt * 这些函数为内部函数, 可能会改变 */ +/** + * 255 -> 00 FF + */ +fun Short.toByteArray(): ByteArray = with(toInt()) { + byteArrayOf( + (shr(8) and 0xFF).toByte(), + (shr(0) and 0xFF).toByte() + ) +} + /** * 255 -> 00 00 00 FF */ fun Int.toByteArray(): ByteArray = byteArrayOf( - (shr(24) and 0xFF).toByte(), - (shr(16) and 0xFF).toByte(), - (shr(8) and 0xFF).toByte(), - (shr(0) and 0xFF).toByte() + ushr(24).toByte(), + ushr(16).toByte(), + ushr(8).toByte(), + ushr(0).toByte() ) /** @@ -104,7 +114,8 @@ fun Byte.fixToUHex(): String = this.toUByte().fixToUHex() /** * 转无符号十六进制表示, 并补充首位 `0`. */ -fun UByte.fixToUHex(): String = if (this.toInt() in 0..15) "0${this.toString(16).toUpperCase()}" else this.toString(16).toUpperCase() +fun UByte.fixToUHex(): String = + if (this.toInt() in 0..15) "0${this.toString(16).toUpperCase()}" else this.toString(16).toUpperCase() /** * 将无符号 Hex 转为 [ByteArray], 有根据 hex 的 [hashCode] 建立的缓存. @@ -133,7 +144,9 @@ fun String.chunkedHexToBytes(): ByteArray = * 这个方法很累, 不建议经常使用. */ fun String.autoHexToBytes(): ByteArray = - this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray() + this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map { + (it[0].toString() + it[1]).toUByte(16).toByte() + }.toList().toByteArray() /** * 将无符号 Hex 转为 [UByteArray], 有根据 hex 的 [hashCode] 建立的缓存. @@ -178,16 +191,24 @@ fun getRandomString(length: Int, vararg charRanges: CharRange): String = * 本函数将 4 个 [Byte] 的 bits 连接得到 [Int] */ fun ByteArray.toUInt(): UInt = - (this[0].toUInt().and(255u) shl 24) + (this[1].toUInt().and(255u) shl 16) + (this[2].toUInt().and(255u) shl 8) + (this[3].toUInt().and(255u) shl 0) + (this[0].toUInt().and(255u) shl 24) + (this[1].toUInt().and(255u) shl 16) + (this[2].toUInt().and(255u) shl 8) + (this[3].toUInt().and( + 255u + ) shl 0) fun ByteArray.toUShort(): UShort = ((this[0].toUInt().and(255u) shl 8) + (this[1].toUInt().and(255u) shl 0)).toUShort() fun ByteArray.toInt(): Int = - (this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and(255) shl 0) + (this[0].toInt().and(255) shl 24) + (this[1].toInt().and(255) shl 16) + (this[2].toInt().and(255) shl 8) + (this[3].toInt().and( + 255 + ) shl 0) /** * 从 [IoBuffer.Pool] [borrow][ObjectPool.borrow] 一个 [IoBuffer] 然后将 [this] 写入. * 注意回收 ([ObjectPool.recycle]) */ -fun ByteArray.toIoBuffer(offset: Int = 0, length: Int = this.size - offset, pool: ObjectPool<IoBuffer> = IoBuffer.Pool): IoBuffer = pool.borrow().let { it.writeFully(this, offset, length); it } \ No newline at end of file +fun ByteArray.toIoBuffer( + offset: Int = 0, + length: Int = this.size - offset, + pool: ObjectPool<IoBuffer> = IoBuffer.Pool +): IoBuffer = pool.borrow().let { it.writeFully(this, offset, length); it } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt similarity index 67% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt index ea9f51f73..128fffbd0 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/InputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/input.kt @@ -20,26 +20,16 @@ import kotlinx.io.core.* import kotlinx.io.pool.useInstance import net.mamoe.mirai.utils.MiraiDebugAPI import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.cryptor.contentToString +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic +import kotlinx.serialization.InternalSerializationApi -@Suppress("NOTHING_TO_INLINE") -inline fun Input.discardExact(n: Short) = this.discardExact(n.toInt()) - -@Suppress("NOTHING_TO_INLINE") -@JvmSynthetic -inline fun Input.discardExact(n: UShort) = this.discardExact(n.toInt()) - -@Suppress("NOTHING_TO_INLINE") -@JvmSynthetic -inline fun Input.discardExact(n: UByte) = this.discardExact(n.toInt()) - -@Suppress("NOTHING_TO_INLINE") -inline fun Input.discardExact(n: Byte) = this.discardExact(n.toInt()) - -fun ByteReadPacket.transferTo(outputStream: OutputStream) { +@OptIn(MiraiInternalAPI::class, InternalSerializationApi::class) +fun ByteReadPacket.copyTo(outputStream: OutputStream) { ByteArrayPool.useInstance { while (this.isNotEmpty) { outputStream.write(it, 0, this.readAvailable(it)) @@ -56,21 +46,21 @@ inline fun <R> ByteReadPacket.useBytes( block(it, n) } -inline fun ByteReadPacket.readPacket( +@MiraiInternalAPI +inline fun ByteReadPacket.readPacketExact( n: Int = remaining.toInt()//not that safe but adequate ): ByteReadPacket = this.readBytes(n).toReadPacket() -inline fun Input.readUByteLVString(): String = String(this.readUByteLVByteArray()) - -inline fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray()) - -inline fun Input.readUByteLVByteArray(): ByteArray = this.readBytes(this.readUByte().toInt()) - -inline fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readUShort().toInt()) +@OptIn(ExperimentalContracts::class) +inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R { + contract { + callsInPlace(block, InvocationKind.EXACTLY_ONCE) + } + return use(block) +} private inline fun <R> inline(block: () -> R): R = block() - typealias TlvMap = MutableMap<Int, ByteArray> inline fun TlvMap.getOrFail(tag: Int): ByteArray { @@ -81,12 +71,14 @@ inline fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteAr return this[tag] ?: error(lazyMessage(tag)) } +@Suppress("FunctionName") @MiraiInternalAPI -inline fun Input.readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = readTLVMap(true, tagSize, suppressDuplication) +inline fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = + _readTLVMap(true, tagSize, suppressDuplication) @MiraiDebugAPI -@Suppress("DuplicatedCode") -fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap { +@Suppress("DuplicatedCode", "FunctionName") +fun Input._readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true): TlvMap { val map = mutableMapOf<Int, ByteArray>() var key = 0 @@ -108,11 +100,14 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica }.toUByte() != UByte.MAX_VALUE) { if (map.containsKey(key)) { + @Suppress("ControlFlowWithEmptyBody") if (!suppressDuplication) { - DebugLogger.error( + /* + @Suppress("DEPRECATION") + MiraiLogger.error( @Suppress("IMPLICIT_CAST_TO_ANY") """ - Error readTLVMap: + Error readTLVMap: duplicated key ${when (tagSize) { 1 -> key.toByte() 2 -> key.toShort() @@ -122,13 +117,13 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica map=${map.contentToString()} duplicating value=${this.readUShortLVByteArray().toUHexString()} """.trimIndent() - ) + )*/ } else { this.discardExact(this.readShort().toInt() and 0xffff) } } else { try { - map[key] = this.readUShortLVByteArray() + map[key] = this.readBytes(readUShort().toInt()) } catch (e: Exception) { // BufferUnderflowException, java.io.EOFException // if (expectingEOF) { // return map @@ -140,10 +135,18 @@ fun Input.readTLVMap(expectingEOF: Boolean = true, tagSize: Int, suppressDuplica return map } -inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length), charset = charset) -inline fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) -inline fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) -@JvmSynthetic -inline fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) +inline fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = + String(this.readBytes(length), charset = charset) -inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) \ No newline at end of file +inline fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = + String(this.readBytes(length.toInt()), charset = charset) + +inline fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = + String(this.readBytes(length.toInt()), charset = charset) + +@JvmSynthetic +inline fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = + String(this.readBytes(length.toInt()), charset = charset) + +inline fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = + String(this.readBytes(length.toInt()), charset = charset) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt similarity index 91% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt index b2d444edd..803e579a6 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/OutputUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/io/output.kt @@ -14,8 +14,9 @@ package net.mamoe.mirai.utils.io import kotlinx.io.core.* +import net.mamoe.mirai.utils.MiraiInternalAPI import net.mamoe.mirai.utils.coerceAtMostOrFail -import net.mamoe.mirai.utils.cryptor.encryptBy +import net.mamoe.mirai.utils.cryptor.TEA import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -67,5 +68,6 @@ fun BytePacketBuilder.writeHex(uHex: String) { /** * 会使用 [ByteArrayPool] 缓存 */ +@OptIn(MiraiInternalAPI::class) inline fun BytePacketBuilder.encryptAndWrite(key: ByteArray, encoder: BytePacketBuilder.() -> Unit) = - BytePacketBuilder().apply(encoder).build().encryptBy(key) { decrypted -> writeFully(decrypted) } \ No newline at end of file + TEA.encrypt(BytePacketBuilder().apply(encoder).build(), key) { decrypted -> writeFully(decrypted) } \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/maps.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/map.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/maps.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/NumberUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt similarity index 100% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/NumberUtils.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/numbers.kt diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt similarity index 57% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt index 4e15bb879..fd5f8a083 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/PlatformUtils.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/platform.kt @@ -12,7 +12,6 @@ package net.mamoe.mirai.utils import io.ktor.client.HttpClient -import kotlinx.io.core.toByteArray /** * 时间戳 @@ -21,31 +20,32 @@ expect val currentTimeMillis: Long inline val currentTimeSeconds: Long get() = currentTimeMillis / 1000 - /** - * 解 zip 压缩 + * 仅供内部使用的工具类. + * 不写为扩展是为了避免污染命名空间. */ -expect fun ByteArray.unzip(offset: Int = 0, length: Int = this.size - offset): ByteArray +@MiraiInternalAPI +expect object MiraiPlatformUtils { + fun unzip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray -/** - * MD5 算法 - * - * @return 16 bytes - */ -expect fun md5(byteArray: ByteArray): ByteArray + fun zip(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray -inline fun md5(str: String): ByteArray = md5(str.toByteArray()) -/** - * Localhost 解析 - */ -expect fun localIpAddress(): String + fun md5(data: ByteArray, offset: Int = 0, length: Int = data.size - offset): ByteArray -/** - * Ktor HttpClient. 不同平台使用不同引擎. - */ -expect val Http: HttpClient + inline fun md5(str: String): ByteArray + fun localIpAddress(): String + + /** + * Ktor HttpClient. 不同平台使用不同引擎. + */ + @MiraiInternalAPI + val Http: HttpClient +} + + +@Suppress("DuplicatedCode") // false positive. `this` is not the same for `List<Byte>` and `ByteArray` internal fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) { require(offset >= 0) { "offset shouldn't be negative: $offset" } require(length >= 0) { "length shouldn't be negative: $length" } diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Time.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/time.kt similarity index 98% rename from mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Time.kt rename to mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/time.kt index b8cfd9ff8..81eba7d3b 100644 --- a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/Time.kt +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/time.kt @@ -14,7 +14,6 @@ package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName -import kotlin.time.seconds // 临时使用, 待 Kotlin Duration 稳定后使用 Duration. // 内联属性, 则将来删除这些 API 将不会导致二进制不兼容. diff --git a/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/tryNTimes.kt b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/tryNTimes.kt new file mode 100644 index 000000000..b336967f7 --- /dev/null +++ b/mirai-core/src/commonMain/kotlin/net.mamoe.mirai/utils/tryNTimes.kt @@ -0,0 +1,68 @@ +/* + * 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.utils + +@PublishedApi +internal expect fun Throwable.addSuppressedMirai(e: Throwable) + +@MiraiInternalAPI +@Suppress("DuplicatedCode") +inline fun <R> tryNTimes(repeat: Int, block: (Int) -> R): R { + var lastException: Throwable? = null + + repeat(repeat) { + try { + return block(it) + } catch (e: Throwable) { + if (lastException == null) { + lastException = e + } else lastException!!.addSuppressedMirai(e) + } + } + + throw lastException!! +} + +@MiraiInternalAPI +@Suppress("DuplicatedCode") +inline fun <R> tryNTimesOrNull(repeat: Int, block: (Int) -> R): R? { + var lastException: Throwable? = null + + repeat(repeat) { + try { + return block(it) + } catch (e: Throwable) { + if (lastException == null) { + lastException = e + } else lastException!!.addSuppressedMirai(e) + } + } + + return null +} + +@MiraiInternalAPI +@Suppress("DuplicatedCode") +inline fun <R> tryNTimesOrException(repeat: Int, block: (Int) -> R): Throwable? { + var lastException: Throwable? = null + + repeat(repeat) { + try { + block(it) + return null + } catch (e: Throwable) { + if (lastException == null) { + lastException = e + } else lastException!!.addSuppressedMirai(e) + } + } + + return lastException!! +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.message.data/CombinedMessageTest.kt b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.message.data/CombinedMessageTest.kt new file mode 100644 index 000000000..04ad0249b --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.message.data/CombinedMessageTest.kt @@ -0,0 +1,155 @@ +package net.mamoe.mirai.message.data + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.time.ExperimentalTime +import kotlin.time.measureTime + +internal class CombinedMessageTest { + + @Test + fun testAsSequence() { + var message: Message = "Hello ".toMessage() + message += "World" + + assertEquals( + "Hello World", + (message as CombinedMessage).asSequence().joinToString(separator = "") + ) + } + + @Test + fun testAsSequence2() { + var message: Message = "Hello ".toMessage() + message += listOf( + PlainText("W"), + PlainText("o"), + PlainText("r") + PlainText("ld") + ).asMessageChain() + + assertEquals( + "Hello World", + (message as CombinedMessage).asSequence().joinToString(separator = "") + ) + } + + private val toAdd = "1".toMessage() + + @OptIn(ExperimentalTime::class) + @Test + fun speedTest() = repeat(100) { + var count = 1L + + repeat(Int.MAX_VALUE) { + count++ + } + + var combineMessage: Message = toAdd + + println( + "init combine ok " + measureTime { + repeat(1000) { + combineMessage += toAdd + } + }.inMilliseconds + ) + + val list = mutableListOf<Message>() + println( + "init messageChain ok " + measureTime { + repeat(1000) { + list += toAdd + } + }.inMilliseconds + ) + + measureTime { + list.joinToString(separator = "") + }.let { time -> + println("list foreach: ${time.inMilliseconds} ms") + } + + measureTime { + (combineMessage as CombinedMessage).iterator().joinToString(separator = "") + }.let { time -> + println("combined iterate: ${time.inMilliseconds} ms") + } + + measureTime { + (combineMessage as CombinedMessage).asSequence().joinToString(separator = "") + }.let { time -> + println("combined sequence: ${time.inMilliseconds} ms") + } + + repeat(5) { + println() + } + } + + @OptIn(ExperimentalTime::class) + @Test + fun testFastIteration() { + println("start!") + println("start!") + println("start!") + println("start!") + + var combineMessage: Message = toAdd + + println( + "init combine ok " + measureTime { + repeat(1000) { + combineMessage += toAdd + } + }.inMilliseconds + ) + + measureTime { + (combineMessage as CombinedMessage).iterator().joinToString(separator = "") + }.let { time -> + println("combine: ${time.inMilliseconds} ms") + } + } +} + +public fun <T> Iterator<T>.joinToString( + separator: CharSequence = ", ", + prefix: CharSequence = "", + postfix: CharSequence = "", + limit: Int = -1, + truncated: CharSequence = "...", + transform: ((T) -> CharSequence)? = null +): String { + return joinTo(StringBuilder(), separator, prefix, postfix, limit, truncated, transform).toString() +} + +public fun <T, A : Appendable> Iterator<T>.joinTo( + buffer: A, + separator: CharSequence = ", ", + prefix: CharSequence = "", + postfix: CharSequence = "", + limit: Int = -1, + truncated: CharSequence = "...", + transform: ((T) -> CharSequence)? = null +): A { + buffer.append(prefix) + var count = 0 + for (element in this) { + if (++count > 1) buffer.append(separator) + if (limit < 0 || count <= limit) { + buffer.appendElement(element, transform) + } else break + } + if (limit >= 0 && count > limit) buffer.append(truncated) + buffer.append(postfix) + return buffer +} + +internal fun <T> Appendable.appendElement(element: T, transform: ((T) -> CharSequence)?) { + when { + transform != null -> append(transform(element)) + element is CharSequence? -> append(element) + element is Char -> append(element) + else -> append(element.toString()) + } +} \ No newline at end of file diff --git a/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils/PlatformUtilsTest.kt b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils/PlatformUtilsTest.kt new file mode 100644 index 000000000..9bf4d397b --- /dev/null +++ b/mirai-core/src/commonTest/kotlin/net.mamoe.mirai.utils/PlatformUtilsTest.kt @@ -0,0 +1,24 @@ +/* + * 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.utils + +import kotlinx.io.core.toByteArray +import net.mamoe.mirai.utils.io.encodeToString +import kotlin.test.Test +import kotlin.test.assertEquals + +internal class PlatformUtilsTest { + + @OptIn(MiraiInternalAPI::class) + @Test + fun testZip() { + assertEquals("test", MiraiPlatformUtils.unzip(MiraiPlatformUtils.zip("test".toByteArray())).encodeToString()) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt new file mode 100644 index 000000000..7df8905d0 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/Bot.kt @@ -0,0 +1,245 @@ +@file:Suppress("unused") + +package net.mamoe.mirai + +import kotlinx.coroutines.io.ByteReadChannel +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.data.AddFriendResult +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.network.BotNetworkHandler +import net.mamoe.mirai.network.LoginFailedException +import net.mamoe.mirai.utils.* + +/** + * 机器人对象. 一个机器人实例登录一个 QQ 账号. + * Mirai 为多账号设计, 可同时维护多个机器人. + * + * 注: Bot 为全协程实现, 没有其他任务时若不使用 [join], 主线程将会退出. + * + * @see Contact 联系人 + * @see kotlinx.coroutines.isActive 判断 [Bot] 是否正常运行中. (在线, 且没有被 [close]) + */ +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn( + MiraiInternalAPI::class, LowLevelAPI::class, MiraiExperimentalAPI::class, JavaHappyAPI::class +) +actual abstract class Bot actual constructor() : CoroutineScope, LowLevelBotAPIAccessor, BotJavaHappyAPI() { + actual companion object { + /** + * 复制一份此时的 [Bot] 实例列表. + */ + @JvmStatic + actual val instances: List<WeakRef<Bot>> + get() = BotImpl.instances.toList() + + /** + * 遍历每一个 [Bot] 实例 + */ + @JvmName("forEachInstanceKotlin") + @JvmSynthetic + actual inline fun forEachInstance(block: (Bot) -> Unit) = BotImpl.forEachInstance(block) + + /** + * 遍历每一个 [Bot] 实例 + */ + @JavaHappyAPI + @JvmName("forEachInstance") + @Suppress("FunctionName") + fun __forEachInstanceForJava__(block: (Bot) -> Unit) = forEachInstance(block) + + /** + * 获取一个 [Bot] 实例, 找不到则 [NoSuchElementException] + */ + @JvmStatic + actual fun getInstance(qq: Long): Bot = BotImpl.getInstance(qq = qq) + } + + /** + * [Bot] 运行的 [Context]. + * + * 在 JVM 的默认实现为 [net.mamoe.mirai.utils.Context] + * 在 Android 实现为 `android.content.Context` + */ + actual abstract val context: Context + + /** + * QQ 号码. 实际类型为 uint + */ + actual abstract val uin: Long + + /** + * 昵称 + */ + @MiraiExperimentalAPI("还未支持") + actual val nick: String + get() = ""// TODO("bot 昵称获取") + + /** + * 日志记录器 + */ + actual abstract val logger: MiraiLogger + + // region contacts + + actual abstract val selfQQ: QQ + + /** + * 机器人的好友列表. 它将与服务器同步更新 + */ + @Deprecated( + "use friends instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("this.friends") + ) + actual abstract val qqs: ContactList<QQ> + + /** + * 机器人的好友列表. 它将与服务器同步更新 + */ + actual abstract val friends: ContactList<QQ> + + /** + * 获取一个好友或一个群. + */ + @Deprecated( + "use getFriend or getGroup instead", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("this.qqs.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException(\"contact id \$id\")") + ) + actual operator fun get(id: Long): Contact { + return this.friends.getOrNull(id) ?: this.groups.getOrNull(id) ?: throw NoSuchElementException("contact id $id") + } + + /** + * 判断是否有这个 id 的好友或群. + * 在一些情况下这可能会造成歧义. 请考虑后使用. + */ + actual operator fun contains(id: Long): Boolean { + return this.friends.contains(id) || this.groups.contains(id) + } + + /** + * 获取一个好友对象. 若没有这个好友, 则会抛出异常 [NoSuchElementException] + */ + actual fun getFriend(id: Long): QQ { + if (id == uin) return selfQQ + return friends.delegate.getOrNull(id) + ?: throw NoSuchElementException("No such friend $id for bot ${this.uin}") + } + + /** + * 机器人加入的群列表. + */ + actual abstract val groups: ContactList<Group> + + /** + * 获取一个机器人加入的群. + * + * @throws NoSuchElementException 当不存在这个群时 + */ + actual fun getGroup(id: Long): Group { + return groups.delegate.getOrNull(id) + ?: throw NoSuchElementException("No such group $id for bot ${this.uin}") + } + + // endregion + + // region network + + /** + * 网络模块 + */ + actual abstract val network: BotNetworkHandler + + /** + * 挂起协程直到 [Bot] 下线. + */ + @JvmName("joinSuspend") + @JvmSynthetic + actual suspend inline fun join() = network.join() + + /** + * 登录, 或重新登录. + * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. + * + * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. + * + * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] + * + * @throws LoginFailedException + */ + @JvmName("loginSuspend") + @JvmSynthetic + actual abstract suspend fun login() + // endregion + + + // region actions + + /** + * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * + * @see Bot.recall (扩展函数) 接受参数 [MessageChain] + * @see _lowLevelRecallFriendMessage 低级 API + * @see _lowLevelRecallGroupMessage 低级 API + */ + @JvmName("recallSuspend") + @JvmSynthetic + actual abstract suspend fun recall(source: MessageSource) + + /** + * 获取图片下载链接 + */ + @JvmName("queryImageUrlSuspend") + @JvmSynthetic + actual abstract suspend fun queryImageUrl(image: Image): String + + /** + * 获取图片下载链接并开始下载. + * + * @see ByteReadChannel.copyAndClose + * @see ByteReadChannel.copyTo + */ + @JvmName("openChannelSuspend") + @JvmSynthetic + actual abstract suspend fun openChannel(image: Image): ByteReadChannel + + /** + * 添加一个好友 + * + * @param message 若需要验证请求时的验证消息. + * @param remark 好友备注 + */ + @JvmName("addFriendSuspend") + @JvmSynthetic + @MiraiExperimentalAPI("未支持") + actual abstract suspend fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult + + // endregion + + /** + * 关闭这个 [Bot], 立即取消 [Bot] 的 [kotlinx.coroutines.SupervisorJob]. + * 之后 [kotlinx.coroutines.isActive] 将会返回 `false`. + * + * **注意:** 不可重新登录. 必须重新实例化一个 [Bot]. + * + * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭 + * + * @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭 + */ + actual abstract fun close(cause: Throwable?) + + @OptIn(LowLevelAPI::class, MiraiExperimentalAPI::class) + actual final override fun toString(): String = "Bot(${uin})" +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt index e1fff684c..d6759268a 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotFactoryJvm.kt @@ -7,6 +7,7 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ +@file:JvmName("BotFactoryJvm") @file:Suppress("FunctionName", "unused") package net.mamoe.mirai @@ -29,13 +30,13 @@ internal val factory: BotFactory = run { """ No BotFactory found. Please ensure that you've added dependency of protocol modules. Available modules: - - net.mamoe:mirai-core-timpc + - net.mamoe:mirai-core-timpc (stays at 0.12.0) - net.mamoe:mirai-core-qqandroid (recommended) You should have at lease one protocol module installed. ------------------------------------------------------- 找不到 BotFactory. 请确保你依赖了至少一个协议模块. 可用的协议模块: - - net.mamoe:mirai-core-timpc + - net.mamoe:mirai-core-timpc (0.12.0 后停止更新) - net.mamoe:mirai-core-qqandroid (推荐) 请添加上述任一模块的依赖(与 mirai-core 版本相同) """.trimIndent() @@ -44,6 +45,7 @@ internal val factory: BotFactory = run { /** * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmName("newBot") @JvmOverloads fun Bot(context: Context, qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = factory.Bot(context, qq, password, configuration) @@ -51,6 +53,7 @@ fun Bot(context: Context, qq: Long, password: String, configuration: BotConfigur /** * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmSynthetic inline fun Bot(context: Context, qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot = factory.Bot(context, qq, password, configuration) @@ -58,6 +61,7 @@ inline fun Bot(context: Context, qq: Long, password: String, configuration: (Bot /** * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmName("newBot") @JvmOverloads fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfiguration.Default): Bot = factory.Bot(ContextImpl(), qq, password, configuration) @@ -65,5 +69,43 @@ fun Bot(qq: Long, password: String, configuration: BotConfiguration = BotConfigu /** * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 */ +@JvmSynthetic inline fun Bot(qq: Long, password: String, configuration: (BotConfiguration.() -> Unit)): Bot = - factory.Bot(ContextImpl(), qq, password, configuration) \ No newline at end of file + factory.Bot(ContextImpl(), qq, password, configuration) + + +/** + * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmName("newBot") +@JvmOverloads +fun Bot( + context: Context, + qq: Long, + passwordMd5: ByteArray, + configuration: BotConfiguration = BotConfiguration.Default +): Bot = + factory.Bot(context, qq, passwordMd5, configuration) + +/** + * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmSynthetic +inline fun Bot(context: Context, qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot = + factory.Bot(context, qq, passwordMd5, BotConfiguration().apply(configuration)) + + +/** + * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmName("newBot") +@JvmOverloads +fun Bot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration = BotConfiguration.Default): Bot = + factory.Bot(ContextImpl(), qq, passwordMd5, configuration) + +/** + * 加载现有协议的 [BotFactory], 并使用指定的 [配置][configuration] 构造 [Bot] 实例 + */ +@JvmSynthetic +inline fun Bot(qq: Long, passwordMd5: ByteArray, configuration: (BotConfiguration.() -> Unit)): Bot = + factory.Bot(ContextImpl(), qq, passwordMd5, BotConfiguration().apply(configuration)) \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotJavaHappyAPI.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotJavaHappyAPI.kt new file mode 100644 index 000000000..5f77238e9 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/BotJavaHappyAPI.kt @@ -0,0 +1,222 @@ +package net.mamoe.mirai + +import kotlinx.coroutines.* +import net.mamoe.mirai.contact.PermissionDeniedException +import net.mamoe.mirai.contact.recall +import net.mamoe.mirai.data.AddFriendResult +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.MessageSource +import net.mamoe.mirai.network.LoginFailedException +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.MiraiInternalAPI +import java.util.concurrent.Future +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException + +/** + * [Bot] 中为了让 Java 使用者调用更方便的 API 列表. + */ +@MiraiInternalAPI +@Suppress("FunctionName", "INAPPLICABLE_JVM_NAME", "unused") +actual abstract class BotJavaHappyAPI actual constructor() { + init { + @Suppress("LeakingThis") + assert(this is Bot) + } + + private inline fun <R> runBlocking(crossinline block: suspend Bot.() -> R): R { + return kotlinx.coroutines.runBlocking { block(this@BotJavaHappyAPI as Bot) } + } + + private inline fun <R> future(crossinline block: suspend Bot.() -> R): Future<R> { + return (this as Bot).run { future { block() } } + } + + /** + * 登录, 或重新登录. + * 这个函数总是关闭一切现有网路任务, 然后重新登录并重新缓存好友列表和群列表. + * + * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. + * + * 最终调用 [net.mamoe.mirai.network.BotNetworkHandler.relogin] + * + * @throws LoginFailedException + */ + @JvmName("login") + fun __loginBlockingForJava__() { + runBlocking { login() } + } + + /** + * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * + * @see Bot.recall (扩展函数) 接受参数 [MessageChain] + */ + @JvmName("recall") + fun __recallBlockingForJava__(source: MessageSource) { + runBlocking { recall(source) } + } + + /** + * 撤回这条消息. + * 根据 [message] 内的 [MessageSource] 进行相关判断. + * + * [Bot] 撤回自己的消息不需要权限. + * [Bot] 撤回群员的消息需要管理员权限. + * + * @throws PermissionDeniedException 当 [Bot] 无权限操作时 + * @see Bot.recall + */ + @JvmName("recall") + fun __recallBlockingForJava__(message: MessageChain) { + runBlocking { recall(message) } + } + + /** + * 在一段时间后撤回这条消息. + * 将根据 [MessageSource.groupId] 判断消息是群消息还是好友消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @see recall + */ + @JvmName("recallIn") + fun __recallIn_MemberForJava__(source: MessageSource, millis: Long) { + runBlocking { recallIn(source, millis) } + } + + /** + * 在一段时间后撤回这条消息. + * + * @param millis 延迟的时间, 单位为毫秒 + * @see recall + */ + @JvmName("recallIn") + fun __recallIn_MemberForJava__(source: MessageChain, millis: Long) { + runBlocking { recallIn(source, millis) } + } + + /** + * 获取图片下载链接 + */ + @JvmName("queryImageUrl") + fun __queryImageUrlBlockingForJava__(image: Image): String { + return runBlocking { queryImageUrl(image) } + } + + /** + * 阻塞当前线程直到 [Bot] 下线. + */ + @JvmName("join") + fun __joinBlockingForJava__() { + runBlocking { join() } + } + + /** + * 添加一个好友 + * + * @param message 若需要验证请求时的验证消息. + * @param remark 好友备注 + */ + @JvmName("addFriend") + fun __addFriendBlockingForJava__( + id: Long, + message: String? = null, + remark: String? = null + ): AddFriendResult { + @OptIn(MiraiExperimentalAPI::class) + return runBlocking { addFriend(id, message, remark) } + } + + /** + * 异步调用 [__loginBlockingForJava__] + */ + @JvmName("loginAsync") + fun __loginAsyncForJava__(): Future<Unit> { + return future { login() } + } + + /** + * 异步调用 [__recallBlockingForJava__] + */ + @JvmName("recallAsync") + fun __recallAsyncForJava__(source: MessageSource): Future<Unit> { + return future { recall(source) } + } + + /** + * 异步调用 [__recallBlockingForJava__] + */ + @JvmName("recallAsync") + fun __recallAsyncForJava__(source: MessageChain): Future<Unit> { + return future { recall(source) } + } + + /** + * 异步调用 [__queryImageUrlBlockingForJava__] + */ + @JvmName("queryImageUrlAsync") + fun __queryImageUrlAsyncForJava__(image: Image): Future<String> { + return future { queryImageUrl(image) } + } +} + +@OptIn(ExperimentalCoroutinesApi::class) +internal fun <R, C : CoroutineScope> C.future(block: suspend C.() -> R): Future<R> { + val future = object : Future<R> { + val value: CompletableDeferred<R> = CompletableDeferred() + + override fun isDone(): Boolean { + return value.isCompleted + } + + override fun get(): R { + if (value.isCompleted) { + return value.getCompleted() + } + return runBlocking { value.await() } + } + + override fun get(timeout: Long, unit: TimeUnit): R { + if (value.isCompleted) { + return value.getCompleted() + } + return runBlocking { + withTimeoutOrNull(TimeUnit.MILLISECONDS.convert(timeout, unit)) { value.await() } + ?: throw TimeoutException() + } + } + + override fun cancel(mayInterruptIfRunning: Boolean): Boolean { + if (value.isCompleted || value.isCancelled) { + return false + } + + return if (mayInterruptIfRunning && value.isActive) { + value.cancel() + true + } else { + false + } + } + + override fun isCancelled(): Boolean { + return value.isCancelled + } + } + + launch { + @OptIn(ExperimentalCoroutinesApi::class) + future.value.completeWith(kotlin.runCatching { block() }) + } + + return future +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/MiraiEnvironmentJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/MiraiEnvironmentJvm.kt deleted file mode 100644 index ceebf145b..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/MiraiEnvironmentJvm.kt +++ /dev/null @@ -1,18 +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 - */ - -@file:Suppress("MayBeConstant", "unused") - -package net.mamoe.mirai - -actual object MiraiEnvironment { - @JvmStatic - actual val platform: Platform - get() = Platform.JVM -} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt new file mode 100644 index 000000000..dc0dca721 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt @@ -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 + */ + +package net.mamoe.mirai.contact + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +import net.mamoe.mirai.event.events.BeforeImageUploadEvent +import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent +import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent +import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineImage +import net.mamoe.mirai.message.data.id +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException +import net.mamoe.mirai.utils.WeakRefProperty + +/** + * 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group]. + * + * @author Him188moe + */ +@Suppress("INAPPLICABLE_JVM_NAME") +@OptIn(MiraiInternalAPI::class, JavaHappyAPI::class) +actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() { + /** + * 这个联系人所属 [Bot]. + */ + @WeakRefProperty + actual abstract val bot: Bot + /** + * 可以是 QQ 号码或者群号码. + * + * 对于 [QQ], `uin` 与 `id` 是相同的意思. + * 对于 [Group], `groupCode` 与 `id` 是相同的意思. + * + * @see QQ.id + * @see Group.id + */ + actual abstract val id: Long + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. + */ + @JvmName("sendMessageSuspend") + @JvmSynthetic + actual abstract suspend fun sendMessage(message: MessageChain): MessageReceipt<out Contact> + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract suspend fun uploadImage(image: ExternalImage): OfflineImage + + /** + * 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同. + * + * 注: + * [id] 相同的 [Member] 和 [QQ], 他们并不 [equals]. + * 因为, [Member] 含义为群员, 必属于一个群. + * 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人. + */ + actual abstract override fun equals(other: Any?): Boolean + + /** + * @return `bot.hashCode() * 31 + id.hashCode()` + */ + actual abstract override fun hashCode(): Int + + /** + * @return "QQ($id)" or "Group($id)" or "Member($id)" + */ + actual abstract override fun toString(): String +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaHappyAPI.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaHappyAPI.kt new file mode 100644 index 000000000..d8cd329c9 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/ContactJavaHappyAPI.kt @@ -0,0 +1,340 @@ +/* + * 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.contact + +import kotlinx.coroutines.Dispatchers +import kotlinx.io.core.Input +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +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.future +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.Message +import net.mamoe.mirai.message.uploadImage +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException +import java.awt.image.BufferedImage +import java.io.File +import java.io.InputStream +import java.net.URL +import java.util.concurrent.Future + +@MiraiInternalAPI +@JavaHappyAPI +@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused") +actual abstract class ContactJavaHappyAPI { + + private inline fun <R> runBlocking(crossinline block: suspend Contact.() -> R): R { + @Suppress("CAST_NEVER_SUCCEEDS") + return kotlinx.coroutines.runBlocking { block(this@ContactJavaHappyAPI as Contact) } + } + + private inline fun <R> future(crossinline block: suspend Contact.() -> R): Future<R> { + @Suppress("CAST_NEVER_SUCCEEDS") + return (this as Contact).run { future { block() } } + } + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. + */ + @Throws(EventCancelledException::class, IllegalStateException::class) + @JvmName("sendMessage") + open fun __sendMessageBlockingForJava__(message: Message): MessageReceipt<Contact> { + return runBlocking { sendMessage(message) } + } + + @JvmName("sendMessage") + open fun __sendMessageBlockingForJava__(message: String): MessageReceipt<Contact> { + return runBlocking { sendMessage(message) } + } + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: ExternalImage): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: URL): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: InputStream): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: Input): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: File): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件 + * @throws OverFileSizeMaxException + */ + @Throws(OverFileSizeMaxException::class) + @JvmName("uploadImage") + open fun __uploadImageBlockingForJava__(image: BufferedImage): Image { + return runBlocking { uploadImage(image) } + } + + /** + * 发送消息 + * @see Contact.sendMessage + */ + @JvmName("sendMessageAsync") + open fun __sendMessageAsyncForJava__(message: Message): Future<MessageReceipt<Contact>> { + return future { sendMessage(message) } + } + + /** + * 发送消息 + * @see Contact.sendMessage + */ + @JvmName("sendMessageAsync") + open fun __sendMessageAsyncForJava__(message: String): Future<MessageReceipt<Contact>> { + return future { sendMessage(message) } + } + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: ExternalImage): Future<Image> { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: URL): Future<Image> { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: InputStream): Future<Image> { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: Input): Future<Image> { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: File): Future<Image> { + return future { uploadImage(image) } + } + + /** + * 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件 + */ + @JvmName("uploadImageAsync") + open fun __uploadImageAsyncForJava__(image: BufferedImage): Future<Image> { + return future { uploadImage(image) } + } +} + +@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused", "unused") +@MiraiInternalAPI +@JavaHappyAPI +actual abstract class MemberJavaHappyAPI : QQ() { + private inline fun <R> runBlocking(crossinline block: suspend Member.() -> R): R { + @Suppress("CAST_NEVER_SUCCEEDS") + return kotlinx.coroutines.runBlocking { block(this@MemberJavaHappyAPI as Member) } + } + + private inline fun <R> future(crossinline block: suspend Member.() -> R): Future<R> { + @Suppress("CAST_NEVER_SUCCEEDS") + return (this as Member).run { future { block() } } + } + + + /** + * 禁言. + * + * QQ 中最小操作和显示的时间都是一分钟. + * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @return 机器人无权限时返回 `false` + * + * @see Int.minutesToSeconds + * @see Int.hoursToSeconds + * @see Int.daysToSeconds + * + * @see MemberMuteEvent 成员被禁言事件 + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("mute") + open fun __muteBlockingForJava__(seconds: Int) { + runBlocking { mute(seconds) } + } + + /** + * 解除禁言. + * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * + * @see MemberUnmuteEvent 成员被取消禁言事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("unmute") + open fun __unmuteBlockingForJava__() { + runBlocking { unmute() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kick") + open fun __kickBlockingForJava__(message: String) { + runBlocking { kick() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kick") + open fun __kickBlockingForJava__() = __kickBlockingForJava__("") + + + /** + * 禁言. + * + * QQ 中最小操作和显示的时间都是一分钟. + * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @return 机器人无权限时返回 `false` + * + * @see Int.minutesToSeconds + * @see Int.hoursToSeconds + * @see Int.daysToSeconds + * + * @see MemberMuteEvent 成员被禁言事件 + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("muteAsync") + open fun __muteAsyncForJava__(seconds: Int): Future<Unit> { + return future { mute(seconds) } + } + + /** + * 解除禁言. + * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * + * @see MemberUnmuteEvent 成员被取消禁言事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("unmuteAsync") + open fun __unmuteAsyncForJava__(): Future<Unit> { + return future { unmute() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kickAsync") + open fun __kickAsyncForJava__(message: String): Future<Unit> { + return future { kick() } + } + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("kickAsync") + open fun __kickAsyncForJava__(): Future<Unit> = __kickAsyncForJava__("") +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt new file mode 100644 index 000000000..c1a6d1c21 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt @@ -0,0 +1,239 @@ +/* + * 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.contact + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot +import net.mamoe.mirai.data.MemberInfo +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.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineGroupImage +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException + +/** + * 群. 在 QQ Android 中叫做 "Troop" + */ +@Suppress("INAPPLICABLE_JVM_NAME") +actual abstract class Group : Contact(), CoroutineScope { + /** + * 群名称. + * + * 在修改时将会异步上传至服务器. + * 频繁修改可能会被服务器拒绝. + * + * @see MemberPermissionChangeEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var name: String + + /** + * 入群公告, 没有时为空字符串. + * + * 在修改时将会异步上传至服务器. + * + * @see GroupEntranceAnnouncementChangeEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var entranceAnnouncement: String + + /** + * 全体禁言状态. `true` 为开启. + * + * 当前仅能修改状态. + * + * @see GroupMuteAllEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var isMuteAll: Boolean + + /** + * 坦白说状态. `true` 为允许. + * + * 在修改时将会异步上传至服务器. + * + * @see GroupAllowConfessTalkEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var isConfessTalkEnabled: Boolean + + /** + * 允许群员邀请好友入群的状态. `true` 为允许 + * + * 在修改时将会异步上传至服务器. + * + * @see GroupAllowMemberInviteEvent + * @throws PermissionDeniedException 无权限修改时将会抛出异常 + */ + actual abstract var isAllowMemberInvite: Boolean + + /** + * 自动加群审批 + */ + actual abstract val isAutoApproveEnabled: Boolean + + /** + * 匿名聊天 + */ + actual abstract val isAnonymousChatEnabled: Boolean + + /** + * 同为 groupCode, 用户看到的群号码. + */ + actual abstract override val id: Long + + /** + * 群主. + * + * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 + */ + actual abstract val owner: Member + + /** + * [Bot] 在群内的 [Member] 实例 + */ + @MiraiExperimentalAPI + actual abstract val botAsMember: Member + + /** + * 机器人被禁言还剩余多少秒 + * + * @see BotMuteEvent 机器人被禁言事件 + * @see isBotMuted 判断机器人是否正在被禁言 + */ + actual abstract val botMuteRemaining: Int + + /** + * 机器人在这个群里的权限 + * + * @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限 + * @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator] + * + * @see BotGroupPermissionChangeEvent 机器人群员修改 + */ + actual abstract val botPermission: MemberPermission + + /** + * 群头像下载链接. + */ + actual val avatarUrl: String + get() = "https://p.qlogo.cn/gh/$id/${id}_1/640" + + /** + * 群成员列表, 不含机器人自己, 含群主. + * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新 + */ + actual abstract val members: ContactList<Member> + + /** + * 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException] + */ + actual abstract operator fun get(id: Long): Member + + /** + * 获取群成员实例, 不存在则 null + */ + actual abstract fun getOrNull(id: Long): Member? + + /** + * 检查此 id 的群成员是否存在 + */ + actual abstract operator fun contains(id: Long): Boolean + + /** + * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun quit(): Boolean + + /** + * 构造一个 [Member]. + * 非特殊情况请不要使用这个函数. 优先使用 [get]. + */ + @JvmName("newMember") + @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName") + @MiraiExperimentalAPI("dangerous") + actual abstract fun Member(memberInfo: MemberInfo): Member + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmName("sendMessageSuspend") + @JvmSynthetic + actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group> + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmName("uploadImageSuspend") + @JvmSynthetic + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineGroupImage + + actual companion object { + /** + * by @kar98k + */ + actual fun calculateGroupUinByGroupCode(groupCode: Long): Long { + var left: Long = groupCode / 1000000L + + when (left) { + in 0..10 -> left += 202 + in 11..19 -> left += 480 - 11 + in 20..66 -> left += 2100 - 20 + in 67..156 -> left += 2010 - 67 + in 157..209 -> left += 2147 - 157 + in 210..309 -> left += 4100 - 210 + in 310..499 -> left += 3800 - 310 + } + + return left * 1000000L + groupCode % 1000000L + } + + actual fun calculateGroupCodeByGroupUin(groupUin: Long): Long { + var left: Long = groupUin / 1000000L + + when (left) { + in 0 + 202..10 + 202 -> left -= 202 + in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11 + in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20 + in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67 + in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157 + in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210 + in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310 + } + + return left * 1000000L + groupUin % 1000000L + } + } + + @MiraiExperimentalAPI + actual fun toFullString(): String { + return "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" + } + +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Member.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Member.kt new file mode 100644 index 000000000..adaeeed5e --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Member.kt @@ -0,0 +1,126 @@ +/* + * 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("unused") + +package net.mamoe.mirai.contact + +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +import net.mamoe.mirai.event.events.* +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.WeakRefProperty + +/** + * 群成员. + */ +@OptIn(MiraiInternalAPI::class, JavaHappyAPI::class) +@Suppress("INAPPLICABLE_JVM_NAME") +actual abstract class Member : MemberJavaHappyAPI() { + /** + * 所在的群. + */ + @WeakRefProperty + actual abstract val group: Group + /** + * 成员的权限, 动态更新. + * + * @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发. + */ + actual abstract val permission: MemberPermission + /** + * 群名片. 可能为空. + * + * 管理员和群主都可修改任何人(包括群主)的群名片. + * + * 在修改时将会异步上传至服务器. + * + * @see [nameCardOrNick] 获取非空群名片或昵称 + * + * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. + * @throws PermissionDeniedException 无权限修改时 + */ + actual abstract var nameCard: String + /** + * 群头衔. + * + * 仅群主可以修改群头衔. + * + * 在修改时将会异步上传至服务器. + * + * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. + * @throws PermissionDeniedException 无权限修改时 + */ + actual abstract var specialTitle: String + /** + * 被禁言剩余时长. 单位为秒. + * + * @see isMuted 判断改成员是否处于禁言状态 + * @see mute 设置禁言 + * @see unmute 取消禁言 + */ + actual abstract val muteTimeRemaining: Int + + /** + * 禁言. + * + * QQ 中最小操作和显示的时间都是一分钟. + * 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. + * + * 管理员可禁言成员, 群主可禁言管理员和群员. + * + * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. + * @return 机器人无权限时返回 `false` + * + * @see Int.minutesToSeconds + * @see Int.hoursToSeconds + * @see Int.daysToSeconds + * + * @see MemberMuteEvent 成员被禁言事件 + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("muteSuspend") + @JvmSynthetic + actual abstract suspend fun mute(durationSeconds: Int) + + /** + * 解除禁言. + * + * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. + * + * @see MemberUnmuteEvent 成员被取消禁言事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("muteSuspend") + @JvmSynthetic + actual abstract suspend fun unmute() + + /** + * 踢出该成员. + * + * 管理员可踢出成员, 群主可踢出管理员和群员. + * + * @see MemberLeaveEvent.Kick 成员被踢出事件. + * @throws PermissionDeniedException 无权限修改时 + */ + @JvmName("muteSuspend") + @JvmSynthetic + actual abstract suspend fun kick(message: String) + + /** + * 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true + */ + actual abstract override fun equals(other: Any?): Boolean + + /** + * @return `bot.hashCode() * 31 + id.hashCode()` + */ + actual abstract override fun hashCode(): Int + +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt new file mode 100644 index 000000000..e91222c8e --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt @@ -0,0 +1,105 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.contact + +import kotlinx.coroutines.CoroutineScope +import net.mamoe.mirai.Bot +import net.mamoe.mirai.data.FriendNameRemark +import net.mamoe.mirai.data.PreviousNameList +import net.mamoe.mirai.data.Profile +import net.mamoe.mirai.event.events.BeforeImageUploadEvent +import net.mamoe.mirai.event.events.EventCancelledException +import net.mamoe.mirai.event.events.ImageUploadEvent +import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent +import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent +import net.mamoe.mirai.message.MessageReceipt +import net.mamoe.mirai.message.data.MessageChain +import net.mamoe.mirai.message.data.OfflineFriendImage +import net.mamoe.mirai.utils.ExternalImage +import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.mirai.utils.OverFileSizeMaxException + +/** + * QQ 对象. + * 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot]. + * 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取. + * + * 对于同一个 [Bot] 任何一个人的 [QQ] 实例都是单一的. + * + * A QQ instance helps you to receive event from or sendPacket event to. + * Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same. + * + * @author Him188moe + */ +@Suppress("INAPPLICABLE_JVM_NAME") +actual abstract class QQ : Contact(), CoroutineScope { + /** + * 请求头像下载链接 + */ + // @MiraiExperimentalAPI + //suspend fun queryAvatar(): AvatarLink + /** + * QQ 号码 + */ + actual abstract override val id: Long + /** + * 昵称 + */ + actual abstract val nick: String + + /** + * 查询用户资料 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun queryProfile(): Profile + + /** + * 头像下载链接 + */ + actual val avatarUrl: String + get() = "http://q1.qlogo.cn/g?b=qq&nk=$id&s=640" + + /** + * 查询曾用名. + * + * 曾用名可能是: + * - 昵称 + * - 共同群内的群名片 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun queryPreviousNameList(): PreviousNameList + + /** + * 查询机器人账号给这个人设置的备注 + */ + @MiraiExperimentalAPI("还未支持") + actual abstract suspend fun queryRemark(): FriendNameRemark + + /** + * 向这个对象发送消息. + * + * @see FriendMessageSendEvent 发送好友信息事件, cancellable + * @see GroupMessageSendEvent 发送群消息事件. cancellable + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 + * + * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) + */ + @JvmName("sendMessageSuspend") + @JvmSynthetic + actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> + + /** + * 上传一个图片以备发送. + * + * @see BeforeImageUploadEvent 图片发送前事件, cancellable + * @see ImageUploadEvent 图片发送完成事件 + * + * @throws EventCancelledException 当发送消息事件被取消 + * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) + */ + @JvmSynthetic + @JvmName("uploadImageSuspend") + actual abstract override suspend fun uploadImage(image: ExternalImage): OfflineFriendImage +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt index 7b17dc366..9dffc3b61 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/events/EventCancelledException.kt @@ -1,7 +1,7 @@ package net.mamoe.mirai.event.events -// 不要删除跨平台结构. +// 不要删除多平台结构. // 否则在 Java 中这个 class 不会被认为是 java.lang.RuntimeException (Kotlin bug) @Suppress("unused") actual class EventCancelledException : RuntimeException { diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/EventInternalJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/EventInternalJvm.kt index ccc2a718f..ddae0cdd2 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/EventInternalJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/EventInternalJvm.kt @@ -16,15 +16,16 @@ import net.mamoe.mirai.event.ListeningStatus import net.mamoe.mirai.utils.MiraiInternalAPI import java.util.function.Consumer import java.util.function.Function +import kotlin.coroutines.EmptyCoroutineContext @MiraiInternalAPI @Suppress("FunctionName") fun <E : Event> Class<E>._subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Function<E, ListeningStatus>): Listener<E> { - return this.kotlin.subscribeInternal(scope.Handler { onEvent.apply(it) }) + return this.kotlin.subscribeInternal(scope.Handler(EmptyCoroutineContext) { onEvent.apply(it) }) } @MiraiInternalAPI @Suppress("FunctionName") fun <E : Event> Class<E>._subscribeEventForJaptOnly(scope: CoroutineScope, onEvent: Consumer<E>): Listener<E> { - return this.kotlin.subscribeInternal(scope.Handler { onEvent.accept(it); ListeningStatus.LISTENING; }) + return this.kotlin.subscribeInternal(scope.Handler(EmptyCoroutineContext) { onEvent.accept(it); ListeningStatus.LISTENING; }) } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/MiraiAtomicBoolean.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/MiraiAtomicBoolean.kt new file mode 100644 index 000000000..0b468cf04 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/event/internal/MiraiAtomicBoolean.kt @@ -0,0 +1,18 @@ +package net.mamoe.mirai.event.internal + +import java.util.concurrent.atomic.AtomicBoolean + + +internal actual class MiraiAtomicBoolean actual constructor(initial: Boolean) { + private val delegate: AtomicBoolean = AtomicBoolean(initial) + + actual fun compareAndSet(expect: Boolean, update: Boolean): Boolean { + return delegate.compareAndSet(expect, update) + } + + actual var value: Boolean + get() = delegate.get() + set(value) { + delegate.set(value) + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt index 6d3ca3cbc..be5b4d1a1 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessagePacket.kt @@ -11,31 +11,28 @@ package net.mamoe.mirai.message -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext +import kotlinx.coroutines.io.ByteWriteChannel import kotlinx.io.core.Input +import kotlinx.io.core.Output import kotlinx.io.core.use -import kotlinx.io.streams.inputStream -import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.QQ import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.toExternalImage +import net.mamoe.mirai.utils.copyAndClose +import net.mamoe.mirai.utils.copyTo import java.awt.image.BufferedImage import java.io.File import java.io.InputStream import java.io.OutputStream import java.net.URL -import javax.imageio.ImageIO /** * 一条从服务器接收到的消息事件. * JVM 平台相关扩展 */ -@UseExperimental(MiraiInternalAPI::class) -actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor(bot: Bot) : MessagePacketBase<TSender, TSubject>(bot) { +@OptIn(MiraiInternalAPI::class) +actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual constructor() : MessagePacketBase<TSender, TSubject>() { // region 上传图片 suspend inline fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) @@ -46,17 +43,15 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con // endregion // region 发送图片 - suspend inline fun sendImage(image: BufferedImage) = subject.sendImage(image) - - suspend inline fun sendImage(image: URL) = subject.sendImage(image) - suspend inline fun sendImage(image: Input) = subject.sendImage(image) - suspend inline fun sendImage(image: InputStream) = subject.sendImage(image) - suspend inline fun sendImage(image: File) = subject.sendImage(image) + suspend inline fun sendImage(image: BufferedImage): MessageReceipt<TSubject> = subject.sendImage(image) + suspend inline fun sendImage(image: URL): MessageReceipt<TSubject> = subject.sendImage(image) + suspend inline fun sendImage(image: Input): MessageReceipt<TSubject> = subject.sendImage(image) + suspend inline fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image) + suspend inline fun sendImage(image: File): MessageReceipt<TSubject> = subject.sendImage(image) // endregion // region 上传图片 (扩展) suspend inline fun BufferedImage.upload(): Image = upload(subject) - suspend inline fun URL.uploadAsImage(): Image = uploadAsImage(subject) suspend inline fun Input.uploadAsImage(): Image = uploadAsImage(subject) suspend inline fun InputStream.uploadAsImage(): Image = uploadAsImage(subject) @@ -64,25 +59,50 @@ actual abstract class MessagePacket<TSender : QQ, TSubject : Contact> actual con // endregion 上传图片 (扩展) // region 发送图片 (扩展) - suspend inline fun BufferedImage.send() = sendTo(subject) - - suspend inline fun URL.sendAsImage() = sendAsImageTo(subject) - suspend inline fun Input.sendAsImage() = sendAsImageTo(subject) - suspend inline fun InputStream.sendAsImage() = sendAsImageTo(subject) - suspend inline fun File.sendAsImage() = sendAsImageTo(subject) + suspend inline fun BufferedImage.send(): MessageReceipt<TSubject> = sendTo(subject) + suspend inline fun URL.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) + suspend inline fun Input.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) + suspend inline fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) + suspend inline fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) // endregion 发送图片 (扩展) // region 下载图片 (扩展) - suspend inline fun Image.downloadTo(file: File): Long = file.outputStream().use { downloadTo(it) } + suspend inline fun Image.downloadTo(file: File) = file.outputStream().use { downloadTo(it) } /** - * 这个函数结束后不会关闭 [output]. 请务必解决好 [OutputStream.close] + * 下载图片到 [output] 但不关闭这个 [output] */ - suspend inline fun Image.downloadTo(output: OutputStream): Long = - download().inputStream().use { input -> withContext(Dispatchers.IO) { input.copyTo(output) } } + suspend inline fun Image.downloadTo(output: OutputStream) = channel().copyTo(output) - suspend inline fun Image.downloadAsStream(): InputStream = download().inputStream() - suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { download().toExternalImage() } + /** + * 下载图片到 [output] 并关闭这个 [output] + */ + suspend inline fun Image.downloadAndClose(output: OutputStream) = channel().copyAndClose(output) + + /** + * 下载图片到 [output] 但不关闭这个 [output] + */ + suspend inline fun Image.downloadTo(output: Output) = channel().copyTo(output) + + /** + * 下载图片到 [output] 并关闭这个 [output] + */ + suspend inline fun Image.downloadAndClose(output: Output) = channel().copyAndClose(output) + + /** + * 下载图片到 [output] 但不关闭这个 [output] + */ + suspend inline fun Image.downloadTo(output: ByteWriteChannel) = channel().copyTo(output) + + /** + * 下载图片到 [output] 并关闭这个 [output] + */ + suspend inline fun Image.downloadAndClose(output: ByteWriteChannel) = channel().copyAndClose(output) + + /* + suspend inline fun Image.downloadAsStream(): InputStream = channel().asInputStream() + suspend inline fun Image.downloadAsExternalImage(): ExternalImage = withContext(Dispatchers.IO) { downloadAsStream().toExternalImage() } suspend inline fun Image.downloadAsBufferedImage(): BufferedImage = withContext(Dispatchers.IO) { ImageIO.read(downloadAsStream()) } + */ // endregion } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt new file mode 100644 index 000000000..6742b2248 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/MessageReceipt.kt @@ -0,0 +1,134 @@ +@file:Suppress("unused") + +package net.mamoe.mirai.message + +import kotlinx.atomicfu.atomic +import kotlinx.coroutines.Job +import kotlinx.coroutines.runBlocking +import net.mamoe.mirai.Bot +import net.mamoe.mirai.JavaHappyAPI +import net.mamoe.mirai.LowLevelAPI +import net.mamoe.mirai.contact.* +import net.mamoe.mirai.message.data.* +import net.mamoe.mirai.recallIn +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.getValue +import net.mamoe.mirai.utils.unsafeWeakRef + +/** + * 发送消息后得到的回执. 可用于撤回. + * + * 此对象持有 [Contact] 的弱引用, [Bot] 离线后将会释放引用, 届时 [target] 将无法访问. + * + * @see Group.sendMessage 发送群消息, 返回回执(此对象) + * @see QQ.sendMessage 发送群消息, 返回回执(此对象) + * + * @see MessageReceipt.sourceId 源 id + * @see MessageReceipt.sourceSequenceId 源序列号 + * @see MessageReceipt.sourceTime 源时间 + */ +@Suppress("FunctionName") +@OptIn(MiraiInternalAPI::class) +actual open class MessageReceipt<C : Contact> actual constructor( + actual val source: MessageSource, + target: C, + private val botAsMember: Member? +) { + init { + require(target is Group || target is QQ) { "target must be either Group or QQ" } + } + + /** + * 发送目标, 为 [Group] 或 [QQ] + */ + actual val target: C by target.unsafeWeakRef() + + /** + * 是否为发送给群的消息的回执 + */ + actual val isToGroup: Boolean = botAsMember != null + + private val _isRecalled = atomic(false) + + /** + * 撤回这条消息. [recall] 或 [recallIn] 只能被调用一次. + * + * @see Bot.recall + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + actual suspend fun recall() { + @Suppress("BooleanLiteralArgument") + if (_isRecalled.compareAndSet(false, true)) { + target.bot.recall(source) + } else error("message is already or planned to be recalled") + } + + /** + * 在一段时间后撤回这条消息.. [recall] 或 [recallIn] 只能被调用一次. + * + * @param millis 延迟时间, 单位为毫秒 + * @throws IllegalStateException 当此消息已经被撤回或正计划撤回时 + */ + actual fun recallIn(millis: Long): Job { + @Suppress("BooleanLiteralArgument") + if (_isRecalled.compareAndSet(false, true)) { + return when (val contact = target) { + is QQ, + is Group -> contact.bot.recallIn(source, millis) + else -> error("Unknown contact type") + } + } else error("message is already or planned to be recalled") + } + + /** + * [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable] 然后引用这条消息. + * @see MessageChain.quote 引用一条消息 + */ + actual open suspend fun quote(): QuoteReplyToSend { + this.source.ensureSequenceIdAvailable() + @OptIn(LowLevelAPI::class) + return _unsafeQuote() + } + + /** + * 引用这条消息, 但不会 [确保 sequenceId可用][MessageSource.ensureSequenceIdAvailable]. + * 在 sequenceId 可用前就发送这条消息则会导致一个异常. + * 当且仅当用于存储而不用于发送时使用这个方法. + * + * @see MessageChain.quote 引用一条消息 + */ + @LowLevelAPI + @Suppress("FunctionName") + actual fun _unsafeQuote(): QuoteReplyToSend { + return this.source.quote(botAsMember as? QQ) + } + + /** + * 引用这条消息并回复. + * @see MessageChain.quote 引用一条消息 + */ + @JvmName("quoteReplySuspend") + @JvmSynthetic + actual suspend fun quoteReply(message: MessageChain) { + target.sendMessage(this.quote() + message) + } + + + @JavaHappyAPI + @JvmName("quoteReply") + fun __quoteReplyBlockingForJava__(message: Message) { + runBlocking { quoteReply(message) } + } + + @JavaHappyAPI + @JvmName("recall") + fun __recallBlockingForJava__() { + runBlocking { recall() } + } + + @JavaHappyAPI + @JvmName("quote") + fun __quoteBlockingForJava__() { + runBlocking { quote() } + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt index 4305e1098..f045ac98f 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/message/SendImageUtilsJvm.kt @@ -16,6 +16,7 @@ import kotlinx.coroutines.withContext import kotlinx.io.core.Input import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.data.Image +import net.mamoe.mirai.message.data.OfflineImage import net.mamoe.mirai.utils.OverFileSizeMaxException import net.mamoe.mirai.utils.sendTo import net.mamoe.mirai.utils.toExternalImage @@ -36,37 +37,41 @@ import java.net.URL * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun BufferedImage.sendTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) +suspend fun <C : Contact> BufferedImage.sendTo(contact: C): MessageReceipt<C> = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) /** * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun URL.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) +suspend fun <C : Contact> URL.sendAsImageTo(contact: C): MessageReceipt<C> = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) /** * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun Input.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) +suspend fun <C : Contact> Input.sendAsImageTo(contact: C): MessageReceipt<C> = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) /** * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun InputStream.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) +suspend fun <C : Contact> InputStream.sendAsImageTo(contact: C): MessageReceipt<C> = + withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) /** * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun File.sendAsImageTo(contact: Contact) { +suspend fun <C : Contact> File.sendAsImageTo(contact: C): MessageReceipt<C> { require(this.exists() && this.canRead()) - withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) + return withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact) } // endregion @@ -78,35 +83,39 @@ suspend fun File.sendAsImageTo(contact: Contact) { * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun BufferedImage.upload(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) +suspend fun BufferedImage.upload(contact: Contact): OfflineImage = + withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) /** * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传后构造 [Image] * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun URL.uploadAsImage(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) +suspend fun URL.uploadAsImage(contact: Contact): OfflineImage = + withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) /** * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传后构造 [Image] * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun Input.uploadAsImage(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) +suspend fun Input.uploadAsImage(contact: Contact): OfflineImage = + withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) /** * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image] * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun InputStream.uploadAsImage(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) +suspend fun InputStream.uploadAsImage(contact: Contact): OfflineImage = + withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) /** * 在 [Dispatchers.IO] 中将文件作为图片上传后构造 [Image] * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend fun File.uploadAsImage(contact: Contact): Image { +suspend fun File.uploadAsImage(contact: Contact): OfflineImage { require(this.exists() && this.canRead()) return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact) } @@ -120,73 +129,75 @@ suspend fun File.uploadAsImage(contact: Contact): Image { * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.sendImage(bufferedImage: BufferedImage) = bufferedImage.sendTo(this) +suspend inline fun <C : Contact> C.sendImage(bufferedImage: BufferedImage): MessageReceipt<C> = + bufferedImage.sendTo(this) /** * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.sendImage(imageUrl: URL) = imageUrl.sendAsImageTo(this) +suspend inline fun <C : Contact> C.sendImage(imageUrl: URL): MessageReceipt<C> = imageUrl.sendAsImageTo(this) /** * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.sendImage(imageInput: Input) = imageInput.sendAsImageTo(this) +suspend inline fun <C : Contact> C.sendImage(imageInput: Input): MessageReceipt<C> = imageInput.sendAsImageTo(this) /** * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.sendImage(imageStream: InputStream) = imageStream.sendAsImageTo(this) +suspend inline fun <C : Contact> C.sendImage(imageStream: InputStream): MessageReceipt<C> = + imageStream.sendAsImageTo(this) /** * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this) +suspend inline fun <C : Contact> C.sendImage(file: File): MessageReceipt<C> = file.sendAsImageTo(this) // endregion // region Contact.uploadImage(IMAGE) /** - * 在 [Dispatchers.IO] 中将图片发送到指定联系人. 不会保存临时文件 + * 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): Image = bufferedImage.upload(this) +suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): OfflineImage = bufferedImage.upload(this) /** - * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 + * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this) +suspend inline fun Contact.uploadImage(imageUrl: URL): OfflineImage = imageUrl.uploadAsImage(this) /** - * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 + * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this) +suspend inline fun Contact.uploadImage(imageInput: Input): OfflineImage = imageInput.uploadAsImage(this) /** - * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 + * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this) +suspend inline fun Contact.uploadImage(imageStream: InputStream): OfflineImage = imageStream.uploadAsImage(this) /** - * 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人 + * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) -suspend inline fun Contact.uploadImage(file: File): Image = file.uploadAsImage(this) +suspend inline fun Contact.uploadImage(file: File): OfflineImage = file.uploadAsImage(this) // endregion \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt index f76ed73fa..3b7726efe 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/BotConfigurationJvm.kt @@ -16,10 +16,10 @@ import kotlinx.coroutines.io.ByteWriteChannel import kotlinx.coroutines.io.close import kotlinx.coroutines.io.jvm.nio.copyTo import kotlinx.coroutines.io.reader +import kotlinx.coroutines.io.writeFully import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext -import kotlinx.io.core.IoBuffer import kotlinx.io.core.use import net.mamoe.mirai.Bot import net.mamoe.mirai.network.BotNetworkHandler @@ -31,64 +31,61 @@ import javax.imageio.ImageIO import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -/** - * 平台默认的验证码识别器. - * - * 可被修改, 除覆盖配置外全局生效. - */ -actual var defaultLoginSolver: LoginSolver = DefaultLoginSolver() - - -internal class DefaultLoginSolver : LoginSolver() { - override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? = loginSolverLock.withLock { +class DefaultLoginSolver( + private val input: suspend () -> String, + private val overrideLogger: MiraiLogger? = null +) : LoginSolver() { + override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock { + val logger = overrideLogger ?: bot.logger val tempFile: File = createTempFile(suffix = ".png").apply { deleteOnExit() } withContext(Dispatchers.IO) { tempFile.createNewFile() - bot.logger.info("需要图片验证码登录, 验证码为 4 字母") + logger.info("需要图片验证码登录, 验证码为 4 字母") try { tempFile.writeChannel().apply { writeFully(data); close() } - bot.logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}") + logger.info("将会显示字符图片. 若看不清字符图片, 请查看文件 ${tempFile.absolutePath}") } catch (e: Exception) { - bot.logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") + logger.info("无法写出验证码文件(${e.message}), 请尝试查看以上字符图片") } tempFile.inputStream().use { val img = ImageIO.read(it) if (img == null) { - bot.logger.info("无法创建字符图片. 请查看文件") + logger.info("无法创建字符图片. 请查看文件") } else { - bot.logger.info(img.createCharImg()) + logger.info(img.createCharImg()) } } } - bot.logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") - return readLine()!!.takeUnless { it.isEmpty() || it.length != 4 }.also { - bot.logger.info("正在提交[$it]中...") + logger.info("请输入 4 位字母验证码. 若要更换验证码, 请直接回车") + return input().takeUnless { it.isEmpty() || it.length != 4 }.also { + logger.info("正在提交[$it]中...") } } override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? = loginSolverLock.withLock { - bot.logger.info("需要滑动验证码") - bot.logger.info("请在任意浏览器中打开以下链接并完成验证码. ") - bot.logger.info("完成后请输入任意字符 ") - bot.logger.info(url) - return readLine().also { - bot.logger.info("正在提交中...") + val logger = overrideLogger ?: bot.logger + logger.info("需要滑动验证码") + logger.info("请在任意浏览器中打开以下链接并完成验证码. ") + logger.info("完成后请输入任意字符 ") + logger.info(url) + return input().also { + logger.info("正在提交中...") } } override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? = loginSolverLock.withLock { - bot.logger.info("需要进行账户安全认证") - bot.logger.info("该账户有[设备锁]/[不常用登陆地点]/[不常用设备登陆]的问题") - bot.logger.info("完成以下账号认证即可成功登陆|理论本认证在mirai每个账户中最多出现1次") - bot.logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符") - bot.logger.info("这步操作将在后续的版本中优化") - bot.logger.info(url) - return readLine().also { - bot.logger.info("正在提交中...") + val logger = overrideLogger ?: bot.logger + logger.info("需要进行账户安全认证") + logger.info("该账户有[设备锁]/[不常用登录地点]/[不常用设备登录]的问题") + logger.info("完成以下账号认证即可成功登录|理论本认证在mirai每个账户中最多出现1次") + logger.info("请将该链接在QQ浏览器中打开并完成认证, 成功后输入任意字符") + logger.info("这步操作将在后续的版本中优化") + logger.info(url) + return input().also { + logger.info("正在提交中...") } } - } // Copied from Ktor CIO @@ -195,15 +192,15 @@ actual open class BotConfiguration actual constructor() { /** * 重连失败后, 继续尝试的每次等待时间 */ - actual var reconnectPeriodMillis: Long = 60.secondsToMillis + actual var reconnectPeriodMillis: Long = 5.secondsToMillis /** * 最多尝试多少次重连 */ - actual var reconnectionRetryTimes: Int = 3 + actual var reconnectionRetryTimes: Int = Int.MAX_VALUE /** * 验证码处理器 */ - actual var loginSolver: LoginSolver = defaultLoginSolver + actual var loginSolver: LoginSolver = LoginSolver.Default actual companion object { /** @@ -253,4 +250,18 @@ inline class FileBasedDeviceInfo @BotConfigurationDsl constructor(val filepath: */ @BotConfigurationDsl companion object ByDeviceDotJson +} + +/** + * 验证码, 设备锁解决器 + */ +actual abstract class LoginSolver { + actual abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? + actual abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? + actual abstract suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? + + actual companion object { + actual val Default: LoginSolver + get() = DefaultLoginSolver(input = { withContext(Dispatchers.IO) { readLine() } ?: error("No standard input") }) + } } \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt index e8318e410..47bfd47db 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/ExternalImageJvm.kt @@ -12,12 +12,12 @@ package net.mamoe.mirai.utils import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.io.ByteReadChannel import kotlinx.coroutines.withContext import kotlinx.io.core.Input import kotlinx.io.core.buildPacket import kotlinx.io.core.copyTo import kotlinx.io.errors.IOException -import kotlinx.io.streams.asInput import kotlinx.io.streams.asOutput import net.mamoe.mirai.utils.io.getRandomString import java.awt.image.BufferedImage @@ -60,22 +60,21 @@ suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withC /** * 读取文件头识别图片属性, 然后构造 [ExternalImage] */ +@OptIn(MiraiInternalAPI::class) @Throws(IOException::class) fun File.toExternalImage(): ExternalImage { val input = ImageIO.createImageInputStream(this) checkNotNull(input) { "Unable to read file(path=${this.path}), no ImageInputStream found" } val image = ImageIO.getImageReaders(input).asSequence().firstOrNull() - ?: error("Unable to read file(path=${this.path}), no ImageReader found") + ?: error("Unable to read file(path=${this.path}), no ImageReader found (file type not supported)") image.input = input - val inputStream = this.inputStream() return ExternalImage( width = image.getWidth(0), height = image.getHeight(0), - md5 = this.inputStream().md5(), // dont change + md5 = MiraiPlatformUtils.md5(this.inputStream()), // dont change imageFormat = image.formatName, - input = inputStream.asInput(), - inputSize = inputStream.available().toLong(), + input = this.inputStream(), filename = this.name ) } @@ -91,10 +90,11 @@ suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO @Throws(IOException::class) fun URL.toExternalImage(): ExternalImage { val file = createTempFile().apply { deleteOnExit() } - file.outputStream().asOutput().use { output -> - openStream().asInput().use { input -> + file.outputStream().use { output -> + openStream().use { input -> input.copyTo(output) } + output.flush() } return file.toExternalImage() } @@ -112,6 +112,7 @@ fun InputStream.toExternalImage(): ExternalImage { val file = createTempFile().apply { deleteOnExit() } file.outputStream().use { this.copyTo(it) + it.flush() } this.close() return file.toExternalImage() @@ -132,6 +133,7 @@ fun Input.toExternalImage(): ExternalImage { val file = createTempFile().apply { deleteOnExit() } file.outputStream().asOutput().use { this.copyTo(it) + it.flush() } return file.toExternalImage() } @@ -140,3 +142,16 @@ fun Input.toExternalImage(): ExternalImage { * 在 [IO] 中进行 [Input.toExternalImage] */ suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() } + +/** + * 保存为临时文件然后调用 [File.toExternalImage]. + */ +suspend fun ByteReadChannel.toExternalImage(): ExternalImage { + val file = createTempFile().apply { deleteOnExit() } + file.outputStream().use { + withContext(IO) { copyTo(it) } + it.flush() + } + + return file.suspendToExternalImage() +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt index 0f8bf91c6..17d60ee0d 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/MiraiLoggerJvm.kt @@ -15,9 +15,12 @@ import java.util.* /** * JVM 控制台日志实现 */ -actual open class PlatformLogger @JvmOverloads internal actual constructor( - override val identity: String? +actual open class PlatformLogger constructor( + override val identity: String? = "Mirai", + open val output: (String) -> Unit ) : MiraiLoggerPlatformBase() { + actual constructor(identity: String?) : this(identity, ::println) + override fun verbose0(message: String?) = println(message, LoggerTextFormat.RESET) override fun verbose0(message: String?, e: Throwable?) { if (message != null) verbose(message.toString()) @@ -52,9 +55,9 @@ actual open class PlatformLogger @JvmOverloads internal actual constructor( val time = SimpleDateFormat("HH:mm:ss", Locale.SIMPLIFIED_CHINESE).format(Date()) if (identity == null) { - println("$color$time : $value") + output("$color$time : $value") } else { - println("$color$identity $time : $value") + output("$color$identity $time : $value") } } } diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/OverFileSizeMaxException.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/OverFileSizeMaxException.kt new file mode 100644 index 000000000..4a31ad010 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/OverFileSizeMaxException.kt @@ -0,0 +1,6 @@ +package net.mamoe.mirai.utils + +/** + * 图片文件过大 + */ +actual class OverFileSizeMaxException : IllegalStateException() \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt index 61366628f..e40057fcd 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/PlatformUtilsJvm.kt @@ -7,76 +7,97 @@ * https://github.com/mamoe/mirai/blob/master/LICENSE */ -@file:Suppress("EXPERIMENTAL_API_USAGE") +@file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") package net.mamoe.mirai.utils import io.ktor.client.HttpClient import io.ktor.client.engine.cio.CIO +import io.ktor.util.KtorExperimentalAPI import kotlinx.io.pool.useInstance import net.mamoe.mirai.utils.io.ByteArrayPool -import java.io.* +import java.io.ByteArrayOutputStream +import java.io.InputStream +import java.io.OutputStream import java.net.InetAddress import java.security.MessageDigest +import java.util.zip.Deflater import java.util.zip.Inflater -actual fun md5(byteArray: ByteArray): ByteArray = MessageDigest.getInstance("MD5").digest(byteArray) - -fun InputStream.md5(): ByteArray = this.use { - val digest = MessageDigest.getInstance("md5") - digest.reset() - this.use { input -> - object : OutputStream() { - override fun write(b: Int) { - digest.update(b.toByte()) - } - }.use { output -> - input.copyTo(output) - } - } - return digest.digest() -} - -fun DataInput.md5(): ByteArray { - val digest = MessageDigest.getInstance("md5") - digest.reset() - val buffer = byteArrayOf(1) - while (true) { - try { - this.readFully(buffer) - } catch (e: EOFException) { - break - } - digest.update(buffer[0]) - } - return digest.digest() -} - -actual fun localIpAddress(): String = InetAddress.getLocalHost().hostAddress - -actual val Http: HttpClient get() = HttpClient(CIO) - -actual fun ByteArray.unzip(offset: Int, length: Int): ByteArray { - this.checkOffsetAndLength(offset, length) - if (length == 0) return ByteArray(0) - - val inflater = Inflater() - inflater.reset() - ByteArrayOutputStream().use { output -> - inflater.setInput(this, offset, length) - ByteArrayPool.useInstance { - while (!inflater.finished()) { - output.write(it, 0, inflater.inflate(it)) - } - } - - inflater.end() - return output.toByteArray() - } -} - /** * 时间戳 */ actual val currentTimeMillis: Long - get() = System.currentTimeMillis() \ No newline at end of file + get() = System.currentTimeMillis() + +@MiraiInternalAPI +actual object MiraiPlatformUtils { + actual fun unzip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val inflater = Inflater() + inflater.reset() + ByteArrayOutputStream().use { output -> + inflater.setInput(data, offset, length) + ByteArrayPool.useInstance { + while (!inflater.finished()) { + output.write(it, 0, inflater.inflate(it)) + } + } + + inflater.end() + return output.toByteArray() + } + } + + actual fun zip(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + if (length == 0) return ByteArray(0) + + val deflater = Deflater() + deflater.setInput(data, offset, length) + deflater.finish() + + ByteArrayPool.useInstance { + return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() } + } + } + + actual fun md5(data: ByteArray, offset: Int, length: Int): ByteArray { + data.checkOffsetAndLength(offset, length) + return MessageDigest.getInstance("MD5").apply { update(data, offset, length) }.digest() + } + + actual inline fun md5(str: String): ByteArray = md5(str.toByteArray()) + + /** + * Ktor HttpClient. 不同平台使用不同引擎. + */ + @OptIn(KtorExperimentalAPI::class) + actual val Http: HttpClient + get() = HttpClient(CIO) + + /** + * Localhost 解析 + */ + actual fun localIpAddress(): String = runCatching { + InetAddress.getLocalHost().hostAddress + }.getOrElse { "192.168.1.123" } + + fun md5(stream: InputStream): ByteArray { + val digest = MessageDigest.getInstance("md5") + digest.reset() + stream.use { input -> + object : OutputStream() { + override fun write(b: Int) { + digest.update(b.toByte()) + } + }.use { output -> + input.copyTo(output) + } + } + return digest.digest() + } + +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt index 8bcbf6f42..c9c6729aa 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/SystemDeviceInfo.kt @@ -14,6 +14,9 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.UnstableDefault import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonConfiguration +import net.mamoe.mirai.utils.MiraiPlatformUtils.localIpAddress +import net.mamoe.mirai.utils.MiraiPlatformUtils.md5 import net.mamoe.mirai.utils.io.getRandomByteArray import net.mamoe.mirai.utils.io.getRandomString import java.io.File @@ -21,21 +24,22 @@ import java.io.File /** * 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存. */ -@UseExperimental(UnstableDefault::class) +@OptIn(UnstableDefault::class) fun File.loadAsDeviceInfo(context: Context = ContextImpl()): DeviceInfo { if (!this.exists() || this.length() == 0L) { return SystemDeviceInfo(context).also { - this.writeText(Json.plain.stringify(SystemDeviceInfo.serializer(), it)) + this.writeText(JSON.stringify(SystemDeviceInfo.serializer(), it)) } } - return Json.nonstrict.parse(DeviceInfoData.serializer(), this.readText()).also { + return JSON.parse(DeviceInfoData.serializer(), this.readText()).also { it.context = context } } +private val JSON = Json(JsonConfiguration.Stable) @Serializable -@UseExperimental(ExperimentalUnsignedTypes::class) +@OptIn(ExperimentalUnsignedTypes::class, MiraiInternalAPI::class) actual open class SystemDeviceInfo actual constructor() : DeviceInfo() { actual constructor(context: Context) : this() { this.context = context diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt new file mode 100644 index 000000000..ab0da8e54 --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/addSuppressed.kt @@ -0,0 +1,18 @@ +package net.mamoe.mirai.utils + +private var isAddSuppressedSupported: Boolean = true + +@PublishedApi +internal actual fun Throwable.addSuppressedMirai(e: Throwable) { + if (this == e) { + return + } + if (!isAddSuppressedSupported) { + return + } + try { + this.addSuppressed(e) + } catch (e: Exception) { + isAddSuppressedSupported = false + } +} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt index 81846b342..3db9de92b 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/ECDHJvm.kt @@ -9,7 +9,8 @@ package net.mamoe.mirai.utils.cryptor -import net.mamoe.mirai.utils.md5 +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.MiraiPlatformUtils import org.bouncycastle.jce.provider.BouncyCastleProvider import java.security.* import java.security.spec.ECGenParameterSpec @@ -20,13 +21,13 @@ import javax.crypto.KeyAgreement actual typealias ECDHPrivateKey = PrivateKey actual typealias ECDHPublicKey = PublicKey -actual class ECDHKeyPair( +internal actual class ECDHKeyPairImpl( private val delegate: KeyPair -) { - actual val privateKey: ECDHPrivateKey get() = delegate.private - actual val publicKey: ECDHPublicKey get() = delegate.public +) : ECDHKeyPair { + override val privateKey: ECDHPrivateKey get() = delegate.private + override val publicKey: ECDHPublicKey get() = delegate.public - actual val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) + override val initialShareKey: ByteArray = ECDH.calculateShareKey(privateKey, initialPublicKey) } @Suppress("FunctionName") @@ -34,14 +35,31 @@ actual fun ECDH() = ECDH(ECDH.generateKeyPair()) actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { actual companion object { - init { + @Suppress("ObjectPropertyName") + private val _isECDHAvailable: Boolean = kotlin.runCatching { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) != null) { + Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME) + } Security.addProvider(BouncyCastleProvider()) - } + ECDHKeyPairImpl(KeyPairGenerator.getInstance("ECDH") + .also { it.initialize(ECGenParameterSpec("secp192k1")) } + .genKeyPair()).let { + calculateShareKey(it.privateKey, it.publicKey) + } // try if it is working + }.isSuccess + + actual val isECDHAvailable: Boolean get() = _isECDHAvailable actual fun generateKeyPair(): ECDHKeyPair { - return ECDHKeyPair(KeyPairGenerator.getInstance("EC", "BC").apply { initialize(ECGenParameterSpec("secp192k1")) }.genKeyPair()) + if (!isECDHAvailable) { + return ECDHKeyPair.DefaultStub + } + return ECDHKeyPairImpl(KeyPairGenerator.getInstance("ECDH") + .also { it.initialize(ECGenParameterSpec("secp192k1")) } + .genKeyPair()) } + @OptIn(MiraiInternalAPI::class) actual fun calculateShareKey( privateKey: ECDHPrivateKey, publicKey: ECDHPublicKey @@ -49,7 +67,7 @@ actual class ECDH actual constructor(actual val keyPair: ECDHKeyPair) { val instance = KeyAgreement.getInstance("ECDH", "BC") instance.init(privateKey) instance.doPhase(publicKey, true) - return md5(instance.generateSecret()) + return MiraiPlatformUtils.md5(instance.generateSecret()) } actual fun constructPublicKey(key: ByteArray): ECDHPublicKey { diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt deleted file mode 100644 index 8d72af7aa..000000000 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/cryptor/contentToString.kt +++ /dev/null @@ -1,54 +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.utils.cryptor - -import net.mamoe.mirai.utils.MiraiDebugAPI -import java.lang.reflect.Field -import kotlin.reflect.full.allSuperclasses - - -val FIELD_TRY_SET_ACCESSIBLE = Field::class.java.declaredMethods.firstOrNull { it.name == "trySetAccessible" } - -@MiraiDebugAPI -actual fun Any.contentToStringReflectively(prefix: String, filter: ((name: String, value: Any?) -> Boolean)?): String { - val newPrefix = prefix + ProtoMap.indent - return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" + - this.allFieldsFromSuperClassesMatching { it.name.startsWith("net.mamoe.mirai") } - .distinctBy { it.name } - .filterNot { it.name.contains("$") || it.name == "Companion" || it.isSynthetic || it.name == "serialVersionUID" } - .filterNot { it.isEnumConstant } - .map { - FIELD_TRY_SET_ACCESSIBLE?.invoke(it, true) ?: kotlin.run { it.isAccessible = true } - val value = it.get(this) - if (filter != null) { - kotlin.runCatching { - if (!filter(it.name, value)) return@map it.name to FIELD_TRY_SET_ACCESSIBLE - } - } - it.name to value - } - .filterNot { it.second === FIELD_TRY_SET_ACCESSIBLE } - .joinToStringPrefixed( - prefix = newPrefix - ) { (name, value) -> - "$name=" + kotlin.runCatching { - if (value == this) "<this>" - else value.contentToString(newPrefix) - }.getOrElse { "<!>" } - } + "\n$prefix}" -} - -internal fun Any.allFieldsFromSuperClassesMatching(classFilter: (Class<out Any>) -> Boolean): Sequence<Field> { - return (this::class.java.takeIf(classFilter)?.declaredFields?.asSequence() ?: sequenceOf<Field>()) + this::class.allSuperclasses - .asSequence() - .map { it.java } - .filter(classFilter) - .flatMap { it.declaredFields.asSequence() } -} \ No newline at end of file diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt index 07f22e1e3..90711ef01 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocket.kt @@ -30,7 +30,10 @@ actual class PlatformSocket : Closeable { private lateinit var socket: Socket actual val isOpen: Boolean - get() = socket.isConnected + get() = + if (::socket.isInitialized) + socket.isConnected + else false actual override fun close() { if (::socket.isInitialized) { @@ -77,7 +80,7 @@ actual class PlatformSocket : Closeable { } } - @UseExperimental(ExperimentalIoApi::class) + @OptIn(ExperimentalIoApi::class) actual suspend fun connect(serverHost: String, serverPort: Int) { withContext(Dispatchers.IO) { socket = Socket(serverHost, serverPort) diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt index da52975bd..1cf48c36f 100644 --- a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/io/PlatformSocketJvm.kt @@ -21,8 +21,6 @@ import java.nio.channels.ReadableByteChannel import java.nio.channels.WritableByteChannel -actual typealias ClosedChannelException = java.nio.channels.ClosedChannelException - /** * 多平台适配的 DatagramChannel. */ diff --git a/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setVisible.kt b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setVisible.kt new file mode 100644 index 000000000..ffecc5e9b --- /dev/null +++ b/mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/utils/setVisible.kt @@ -0,0 +1,8 @@ +package net.mamoe.mirai.utils + +import kotlin.reflect.KProperty1 +import kotlin.reflect.jvm.javaField + +internal actual fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? { + return this.javaField?.apply { isAccessible = true }?.get(receiver) +} \ No newline at end of file diff --git a/mirai-core/src/jvmTest/kotlin/mirai/test/testCaptchaPacket/TestCaptchaPacket.kt b/mirai-core/src/jvmTest/kotlin/mirai/test/testCaptchaPacket/TestCaptchaPacket.kt index 117b07b76..a603177bb 100644 --- a/mirai-core/src/jvmTest/kotlin/mirai/test/testCaptchaPacket/TestCaptchaPacket.kt +++ b/mirai-core/src/jvmTest/kotlin/mirai/test/testCaptchaPacket/TestCaptchaPacket.kt @@ -9,16 +9,20 @@ package mirai.test.testCaptchaPacket -import net.mamoe.mirai.utils.cryptor.decryptBy +import net.mamoe.mirai.utils.MiraiInternalAPI +import net.mamoe.mirai.utils.cryptor.TEA.decrypt import net.mamoe.mirai.utils.io.hexToBytes import net.mamoe.mirai.utils.io.toUHexString +@MiraiInternalAPI fun main() { val key = "65 F7 F3 14 E3 94 10 1F DD 95 84 A3 F5 9F AD 94".hexToBytes() val data = - "8D 4F 6A 70 F8 4A DE 43 AF 75 D1 3F 3A 3F F2 E0 A8 16 1A 46 13 CD B0 51 45 00 29 52 57 75 6D 4A 4C D9 B7 98 8C B0 96 EC 57 4E 67 FB 8D C5 F1 BF 72 38 40 42 19 54 C2 28 F4 72 C8 AE 24 EB 66 B5 D0 45 0B 72 44 81 E2 F6 2B EE C3 85 93 BA CB B7 72 F4 1A 30 F9 5B 3D B0 79 3E F4 0B F2 1A A7 49 60 3B 37 02 60 0C 5D D5 76 76 47 4F B5 B3 F5 CA 58 6C FC D2 41 3E 24 D1 FB 0A 18 53 D8 E5 A5 85 A8 BC 51 54 3B 66 5B 21 C6 7B AF C9 62 F0 AA 9C CF 2E 84 0F CC 15 5B 35 93 49 5C E4 28 49 A7 8A D3 30 A9 6E 36 4E 7A 49 28 69 4D C3 25 39 6E 45 6E 40 F2 86 1E F4 4F 00 A6 9D E6 9B 84 19 69 C1 31 6A 17 BA F0 0D 8A 22 09 86 24 92 F7 22 C3 47 7F F2 BF 94 8A 8A B5 29".hexToBytes() - .decryptBy(key) + decrypt( + "8D 4F 6A 70 F8 4A DE 43 AF 75 D1 3F 3A 3F F2 E0 A8 16 1A 46 13 CD B0 51 45 00 29 52 57 75 6D 4A 4C D9 B7 98 8C B0 96 EC 57 4E 67 FB 8D C5 F1 BF 72 38 40 42 19 54 C2 28 F4 72 C8 AE 24 EB 66 B5 D0 45 0B 72 44 81 E2 F6 2B EE C3 85 93 BA CB B7 72 F4 1A 30 F9 5B 3D B0 79 3E F4 0B F2 1A A7 49 60 3B 37 02 60 0C 5D D5 76 76 47 4F B5 B3 F5 CA 58 6C FC D2 41 3E 24 D1 FB 0A 18 53 D8 E5 A5 85 A8 BC 51 54 3B 66 5B 21 C6 7B AF C9 62 F0 AA 9C CF 2E 84 0F CC 15 5B 35 93 49 5C E4 28 49 A7 8A D3 30 A9 6E 36 4E 7A 49 28 69 4D C3 25 39 6E 45 6E 40 F2 86 1E F4 4F 00 A6 9D E6 9B 84 19 69 C1 31 6A 17 BA F0 0D 8A 22 09 86 24 92 F7 22 C3 47 7F F2 BF 94 8A 8A B5 29".hexToBytes(), + key + ) println(data.toUHexString()) //00 02 00 00 08 04 01 E0 00 00 04 56 00 00 00 01 00 00 15 E3 01 00 38 58 CE A0 12 81 31 5C 5E 36 23 5B E4 0E 05 A6 47 BF 7C 1A 7A 35 37 59 90 17 50 66 0C 07 03 77 E4 48 DB 28 0A CF C3 A9 B7 C0 95 D3 9D 00 AA A5 EB FB D6 85 8D 10 61 5A D0 01 03 00 19 02 CA 53 7E F0 7B 32 82 EC 9F DE CF 51 8B A4 93 26 76 EC 42 1C 02 00 74 58 14 00 05 00 00 00 00 00 04 6C 73 64 61 00 40 CE 99 84 E8 F1 59 31 B0 3F 6C 4D 44 09 E4 82 77 96 67 03 A7 3A EA 8F 36 B9 20 79 7E C9 0F 75 3C 2A C3 E1 E5 C6 00 B3 5E 91 5B 47 63 EF AF 30 C0 48 2F 58 23 96 CF 65 2F 4C 75 95 A6 CA 5A 2C 5C 00 10 E1 50 C9 F4 F6 F4 2F D1 7F E9 8C AB B6 1C 38 7B diff --git a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt index 413a510ce..ea206952d 100644 --- a/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt +++ b/mirai-core/src/jvmTest/kotlin/net/mamoe/mirai/utils/LockFreeLinkedListTest.kt @@ -56,16 +56,15 @@ internal class LockFreeLinkedListTest { //} } + @Suppress("UNREACHABLE_CODE", "DeferredResultUnused") @Test fun `so many concurrent add remove and foreach`() = runBlocking { + return@runBlocking // 测试通过了, 加快速度. 因为 kotlin 一些其他 bug val list = LockFreeLinkedList<Int>() val addJob = async { list.concurrentDo(2, 30000) { addLast(1) } } //delay(1) // let addJob fly - if (addJob.isCompleted) { - println("Number of elements are not enough") - } val foreachJob = async { list.concurrentDo(1, 10000) { forEach { it + it } @@ -143,7 +142,7 @@ internal class LockFreeLinkedListTest { list.size shouldBeEqualTo 0 } - @UseExperimental(ExperimentalUnsignedTypes::class) + @OptIn(ExperimentalUnsignedTypes::class) @Test fun withInlineClassElements() { val list = LockFreeLinkedList<UInt>() @@ -162,7 +161,7 @@ internal class LockFreeLinkedListTest { println("Check value") value shouldBeEqualTo 6 println("Check size") - println(list.getLinkStructure()) +// println(list.getLinkStructure()) list.size shouldBeEqualTo 6 } @@ -175,7 +174,7 @@ internal class LockFreeLinkedListTest { println("Check value") value shouldBeEqualTo 2 println("Check size") - println(list.getLinkStructure()) +// println(list.getLinkStructure()) list.size shouldBeEqualTo 5 } @@ -199,7 +198,7 @@ internal class LockFreeLinkedListTest { println("Check value") value shouldBeEqualTo 2 println("Check size") - println(list.getLinkStructure()) +// println(list.getLinkStructure()) list.size shouldBeEqualTo 1 } /* @@ -271,7 +270,7 @@ internal class LockFreeLinkedListTest { */ } -@UseExperimental(ExperimentalCoroutinesApi::class) +@OptIn(ExperimentalCoroutinesApi::class) @MiraiExperimentalAPI internal suspend inline fun <E : LockFreeLinkedList<*>> E.concurrentDo(numberOfCoroutines: Int, times: Int, crossinline todo: E.() -> Unit) = coroutineScope { diff --git a/mirai-core/src/main/AndroidManifest.xml b/mirai-core/src/main/AndroidManifest.xml index 576ac07c9..544a92e64 100644 --- a/mirai-core/src/main/AndroidManifest.xml +++ b/mirai-core/src/main/AndroidManifest.xml @@ -2,6 +2,4 @@ <manifest package="net.mamoe.mirai" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> - <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> - <uses-permission android:name="android.permission.READ_PHONE_STATE"/> </manifest> \ No newline at end of file diff --git a/mirai-debug/build.gradle.kts b/mirai-debug/build.gradle.kts deleted file mode 100644 index 907fa54a9..000000000 --- a/mirai-debug/build.gradle.kts +++ /dev/null @@ -1,80 +0,0 @@ -plugins { - id("application") - id("org.openjfx.javafxplugin") version "0.0.8" - kotlin("jvm") - java - id("kotlinx-serialization") -} - -javafx { - version = "11" - modules = listOf("javafx.controls") - //mainClassName = "Application" -} - -application { - mainClassName = "Application" -} - -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 serializationVersion: String by rootProject.ext - -val klockVersion: String by rootProject.ext -val ktorVersion: String by rootProject.ext - -kotlin { - sourceSets { - all { - languageSettings.enableLanguageFeature("InlineClasses") - - languageSettings.useExperimentalAnnotation("kotlin.Experimental") - } - } -} - -fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" - -fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" - -dependencies { - - runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // IDE bug - runtimeOnly(files("../mirai-core-qqandroid/build/classes/kotlin/jvm/main")) // IDE bug - implementation(project(":mirai-core-qqandroid")) - // runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE - - implementation("org.bouncycastle:bcprov-jdk15on:1.64") - - implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion") - - implementation("org.pcap4j:pcap4j-distribution:1.8.2") - implementation("no.tornado:tornadofx:1.7.17") - implementation(group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-javafx", version = "1.3.2") - - implementation(kotlin("stdlib", kotlinVersion)) - implementation("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") - implementation(kotlinx("io-jvm", kotlinXIoVersion)) - implementation(kotlinx("io", kotlinXIoVersion)) - implementation(kotlinx("coroutines-io", coroutinesIoVersion)) - implementation(kotlinx("coroutines-core", coroutinesVersion)) - - implementation(kotlinx("serialization-runtime", serializationVersion)) - - - implementation(ktor("http-cio", ktorVersion)) - implementation(ktor("http", ktorVersion)) - implementation(ktor("client-core-jvm", ktorVersion)) - implementation(ktor("client-cio", ktorVersion)) - implementation(ktor("client-core", ktorVersion)) - implementation(ktor("network", ktorVersion)) - - testImplementation(kotlin("test-annotations-common")) - testImplementation(kotlin("test")) - testImplementation(kotlin("test-junit")) - testImplementation(kotlin("script-runtime")) - -} \ No newline at end of file diff --git a/mirai-debug/lib/jpcap.jar b/mirai-debug/lib/jpcap.jar deleted file mode 100644 index 844721b12..000000000 Binary files a/mirai-debug/lib/jpcap.jar and /dev/null differ diff --git a/mirai-debug/src/main/java/japttest/SuspendTest.java b/mirai-debug/src/main/java/japttest/SuspendTest.java deleted file mode 100644 index ac2ee6343..000000000 --- a/mirai-debug/src/main/java/japttest/SuspendTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package japttest; - -@SuppressWarnings("unused") -public class SuspendTest { - - public static void main(String[] args) { - // TODO: 2019/12/6 Kotlin or IDE bug here - // Uncomment the following line. - // boolean bool = JaptTestKt.getLoginResult() == LoginResult.YOU_CAN_WRITE_ANY_THING; - - System.out.println("Hello world"); - - } -} diff --git a/mirai-debug/src/main/kotlin/DecryptTest.kt b/mirai-debug/src/main/kotlin/DecryptTest.kt deleted file mode 100644 index fe82ad183..000000000 --- a/mirai-debug/src/main/kotlin/DecryptTest.kt +++ /dev/null @@ -1,72 +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 - */ - -@file:Suppress("UNUSED_VARIABLE") - -import net.mamoe.mirai.utils.io.encodeToString -import net.mamoe.mirai.utils.io.hexToBytes -import net.mamoe.mirai.utils.io.toUHexString -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.util.* -import java.util.zip.GZIPInputStream - - -fun main() { - val short: Short = 0x8b1f.toShort() - // 7字节相同| |18字节向东 - val bytes = - "AA 02 56 30 01 3A 40 53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00" - .hexToBytes() - println(bytes.encodeToString()) - - val string = bytes.encodeToString() - - // println("53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64".hexToBytes().encodeToString()) - println("53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64" - .hexToBytes().unbase64().encodeToString() - ) - println("53 4B 4B 2F 6F 59 33 42 39 2F 68 56 54 45 4B 65 6A 5A 39 35 45 4D 7A 68 5A 2F 6F 4A 42 79 35 36 61 6F 50 59 32 6E 51 49 77 41 67 37 47 51 33 34 65 72 43 4C 41 72 50 4B 56 39 35 43 76 65 34 64" - .hexToBytes().unbase64().toUHexString()) - - //base64解密结果 48 A2 BF A1 8D C1 F7 F8 55 4C 42 9E 8D 9F 79 10 CC E1 67 FA 09 07 2E 7A 6A 83 D8 DA 74 08 C0 08 3B 19 0D F8 7A B0 8B 02 B3 CA 57 DE 42 BD EE 1D - - - println() - println() - - - println(Base64.getEncoder().encodeToString(".".repeat(1000).toByteArray())) - - // 01 78 - val data2 = - "9C CD 92 BB 4E C3 30 14 86 77 9E C2 32 23 4A 73 69 D3 76 48 52 25 25 BD 48 84 56 54 15 97 05 85 C4 09 2E B9 A8 4E D2 92 6E DD 10 0C 88 81 0D 84 90 60 40 42 C0 C4 D6 C7 69 E8 63 E0 94 22 46 46 F8 2D 59 F2 B1 7F 9F E3 EF 58 AA 9D FA 1E 18 21 12 E1 30 90 21 5F E0 20 40 81 15 DA 38 70 65 98 C4 0E 53 85 35 65 0D 50 49 7E E4 82 23 82 91 23 C3 C2 3F 17 04 FE A1 83 3D B4 6D FA 48 86 42 45 2F F3 82 58 64 CA A2 2E 32 25 AD 51 66 54 AD 21 32 AA AA AB 1C 5F 15 04 AE 5E F9 76 F4 F0 84 3A 28 05 D3 8A 97 48 46 18 8D 8D C4 8B B1 11 B9 10 38 9E 49 B9 14 21 88 10 19 61 0B B5 37 E9 4A CC CD 04 45 D8 96 E1 38 ED B7 BA 9D 81 53 2F A5 7B A1 A8 B1 29 6E 69 EE C4 68 75 DB FE 46 C7 76 3B 27 BA 67 F4 93 DD 83 DE 7E 33 16 06 CD B2 C6 0F 35 42 D8 52 63 C7 DE 1A 0E 86 1A 04 0A 90 70 8C 7C E0 99 69 98 C4 B4 27 90 46 62 1C 7B 48 01 7F CD F5 37 01 89 5D 55 0A A4 63 A2 48 2C 9D 56 C5 03 2B F4 42 22 C3 F5 2A 97 0F FA A8 EC EE F1 E3 E6 82 CF 6E EF 17 B3 E7 F9 E5 55 F6 7E 96 4D 5F C1 CF 1D 12 9B 83 50 A4 28 4C 88 85 40 B0 6C E6 62 7A 3E 7F 78 5A BC BC CD 67 D7 90 66 F8 DA CC 0F D3 FF A9 7C 02 73 51 C1 65".replace( - " ", - " " - ) - .hexToBytes() - println(data2.size) - println(data2.unbase64().toUHexString()) - -} - -fun ByteArray.unbase64() = Base64.getDecoder().decode(this) - -fun ByteArray.ungzip(): ByteArray { - val out = ByteArrayOutputStream() - val `in` = ByteArrayInputStream(this) - val ungzip = GZIPInputStream(`in`) - val buffer = ByteArray(256) - var n: Int - while (ungzip.read(buffer).also { n = it } >= 0) { - out.write(buffer, 0, n) - } - - return out.toByteArray() -} \ No newline at end of file diff --git a/mirai-debug/src/main/kotlin/HexDebuggerGui.kt b/mirai-debug/src/main/kotlin/HexDebuggerGui.kt deleted file mode 100644 index 944b82540..000000000 --- a/mirai-debug/src/main/kotlin/HexDebuggerGui.kt +++ /dev/null @@ -1,307 +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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE") - -import javafx.geometry.Pos -import javafx.scene.control.TextArea -import javafx.scene.control.TextField -import javafx.scene.layout.Region -import javafx.scene.paint.Color -import javafx.scene.text.FontWeight -import kotlinx.coroutines.* -import kotlinx.io.core.readUByte -import kotlinx.io.core.readUInt -import kotlinx.io.core.readUShort -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.readUVarInt -import tornadofx.* -import java.awt.Toolkit -import java.awt.datatransfer.DataFlavor - - -/** - * How to run: - * - * `gradle :mirai-debug:run` - */ -class Application : App(HexDebuggerGui::class, Styles::class) - -class Styles : Stylesheet() { - companion object { - // Define css classes - val heading by cssclass() - - // Define colors - val mainColor = c("#bdbd22") - } - - init { - heading { - textFill = mainColor - fontSize = 20.px - fontWeight = FontWeight.BOLD - } - - button { - padding = box(vertical = 5.px, horizontal = 15.px) - fontWeight = FontWeight.BOLD - fontSize = 16.px - } - - label { - padding = box(vertical = 5.px, horizontal = 15.px) - fontSize = 16.px - } - - textField { - padding = box(vertical = 5.px, horizontal = 15.px) - } - - textArea { - fontSize = 18.px - } - - val flat = mixin { - backgroundInsets += box(0.px) - borderColor += box(Color.DARKGRAY) - } - - s(button, textInput) { - +flat - } - } -} - -class HexDebuggerGui : View("Mirai Hex Debugger") { - private lateinit var input: TextArea - private lateinit var outSize: TextField - private lateinit var outUVarInt: TextField - private lateinit var outShort: TextField - private lateinit var outUInt: TextField - private lateinit var outString: TextArea - private lateinit var outBits: TextField - - - private val clip = Toolkit.getDefaultToolkit().systemClipboard - private val clipboardContent: String? - get() { - val trans = clip.getContents(null) - if (trans?.isDataFlavorSupported(DataFlavor.stringFlavor) == true) { - return try { - trans.getTransferData(DataFlavor.stringFlavor) as String - } catch (e: Exception) { - e.printStackTrace() - null - } - } - return null - } - - init { - GlobalScope.launch { - var last = clipboardContent - while (true) { - delay(100) - - val current = try { - clipboardContent - } catch (e: Exception) { - e.printStackTrace() - null - } - - try { - if (current != last) { - withContext(Dispatchers.Main) { - input.text = current - updateOutputs( - current?.lineSequence() - ?.map { it.substringBefore("//") } - ?.joinToString(" ") - .toString() - .replace("UVarInt", "", ignoreCase = true) - .replace("[", "") - .replace("]", "") - .replace("(", "") - .replace(")", "") - .replace(" ", " ") - .replace(" ", " ") - .replace(" ", " ") - .replace("_", "") - .replace(",", "") - .replace("`", "") - .replace("\"", "") - .replace("'", "") - .replace("*", "") - .replace("\\", "") - .replace("/", "") - .replace("-", "") - ) - } - } - } finally { - last = current - } - } - - } - } - - private fun updateOutputs(value: String) { - outUVarInt.text = runOrNull { - value.hexToBytes().read { - readUVarInt().toString() - } - } - - outShort.text = runOrNull { - value.hexToBytes().read { - readShort().toString() - } - } - - outUInt.text = runOrNull { - value.hexToBytes().read { - readUInt().toString() - } - } - - outSize.text = runOrNull { - value.hexToBytes().size.toString() - } - - outBits.text = runOrNull { - value.hexToBytes().read { - when (remaining.toInt()) { - 0 -> null - 1 -> readUByte().toString(2) - 2 -> readUShort().toString(2) - 4 -> readUInt().toString(2) - else -> "" - } - } - } - - outString.text = runOrNull { - value.hexToBytes().encodeToString() - } - } - - override val root = hbox { - //prefWidth = 735.0 - minHeight = 300.0 - prefHeight = minHeight - - input = textarea { - promptText = "Input" - paddingVertical = 10 - - prefWidth = 150.0 - minWidth = 100.0 - //maxWidth = 150.0 - fitToHeight(this@hbox) - } - - vbox(10) { - label(" => ") { - alignment = Pos.CENTER - spacing = 10.0 - } - - button("_") { - paddingAll = 0.0 - setOnMouseClicked { - if (primaryStage.isAlwaysOnTop) { - primaryStage.isAlwaysOnTop = false - text = "_" - } else { - primaryStage.isAlwaysOnTop = true - text = "↑" - } - } - } - - alignment = Pos.CENTER - fitToHeight(this@hbox) - } - - vbox(10) { - paddingTop = 10 - alignment = Pos.TOP_RIGHT - label("size") - label("UVarInt") - label("short") - label("uint") - label("bits") - label("string") - children.filterIsInstance<Region>().forEach { - it.fitToParentWidth() - } - } - - vbox(20) { - alignment = Pos.CENTER_RIGHT - - outSize = textfield { - promptText = "Size" - isEditable = false - } - - outUVarInt = textfield { - promptText = "UVarInt" - isEditable = false - } - - outShort = textfield { - promptText = "Short" - isEditable = false - } - - outUInt = textfield { - promptText = "UInt" - isEditable = false - } - - outBits = textfield { - promptText = "Bits" - isEditable = false - } - - outString = textarea { - promptText = "String" - isEditable = false - maxWidth = 100.0 - minHeight = 30.0 - } - - children.filterIsInstance<Region>().forEach { - it.fitToParentWidth() - } - } - - input.textProperty().onChange { - if (it == null) { - return@onChange - } - updateOutputs(it) - } - } -} - - -private inline fun <T> runOrNull(block: () -> T?): T? { - return try { - block() - } catch (e: Exception) { - null - } -} \ No newline at end of file diff --git a/mirai-debug/src/main/kotlin/hex/HexComparator.kt b/mirai-debug/src/main/kotlin/hex/HexComparator.kt deleted file mode 100644 index 3a69048bb..000000000 --- a/mirai-debug/src/main/kotlin/hex/HexComparator.kt +++ /dev/null @@ -1,335 +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 - */ - -@file:Suppress("ObjectPropertyName", "MayBeConstant", "NonAsciiCharacters", "SpellCheckingInspection", "unused", "EXPERIMENTAL_UNSIGNED_LITERALS") - -package hex - -import kotlinx.io.core.toByteArray -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.io.hexToBytes -import net.mamoe.mirai.utils.io.read -import net.mamoe.mirai.utils.io.readUVarInt -import net.mamoe.mirai.utils.io.toUHexString -import kotlin.math.max -import kotlin.reflect.KProperty0 - -/** - * 匹配已知 hex 常量并格式化后打印到控制台. - * - * 低效率, 仅调试使用. - */ -internal fun String.printColorize(ignoreUntilFirstConst: Boolean): String = with(HexComparator) { colorize(ignoreUntilFirstConst) } - -/** - * 比较两个 hex 并格式化后打印到控制台. - * - * 低效率, 仅调试使用. - */ -@MiraiInternalAPI -fun printCompareHex(hex1s: String, hex2s: String): String = with(HexComparator) { - compare( - hex1s.toUpperCase(), - hex2s.toUpperCase() - ) -} - -data class NamedHexElement( - val name: String, - val value: String -) - -/** - * 初始化用于匹配的 Hex 列表 - */ -private fun LinkedHashSet<NamedHexElement>.initConstFileds() { - listOf( - TestConsts, - //TIMProtocol, - PacketIds - ).forEach { obj -> - obj::class.members.filterIsInstance<KProperty0<*>>().forEach { property -> - property.get()?.let { add(NamedHexElement(property.name, it.toString())) } - } - } -} - -private object TestConsts { - val NIU_BI = "牛逼".toByteArray().toUHexString() - val _1994701021 = 1994701021u.toUHexString(" ") - val _1040400290 = 1040400290u.toUHexString(" ") - val _580266363 = 580266363u.toUHexString(" ") - val _761025446 = 761025446u.toUHexString() - - val _1040400290_ = "3E 03 3F A2" - val _1994701021_ = "76 E4 B8 DD" - val _jiahua_ = "B1 89 BE 09" - val _Him188moe_ = "Him188moe".toByteArray().toUHexString() - val 发图片2 = "发图片2".toByteArray().toUHexString() - val 发图片群 = "发图片群".toByteArray().toUHexString() - val 发图片 = "发图片".toByteArray().toUHexString() - val 群 = "群".toByteArray().toUHexString() - val 你好 = "你好".toByteArray().toUHexString() - - val MESSAGE_TAIL_10404 = - "0E 00 07 01 00 04 00 00 00 09 19 00 18 01 00 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00" - .replace(" ", " ") - - val FONT_10404 = "E5 BE AE E8 BD AF E9 9B 85 E9 BB 91" - - val varint580266363 = "FB D2 D8 94 02" - val varint1040400290 = "A2 FF 8C F0 03" - val varint1994701021 = "DD F1 92 B7 07" - val varint761025446 = 761025446u.toUHexString().hexToBytes().read { readUVarInt() }.toUHexString() -} - -@Suppress("SpellCheckingInspection") -private object PacketIds { - val heartbeat = "00 58" - val friendmsgsend = "00 CD" - val friendmsgevent = "00 CE" - val groupmsg = "00 02" -} - -/** - * Hex 比较器, 并着色已知常量 - * - * This could be used to check packet encoding.. - * but better to run under UNIX - * - * @author NaturalHG - * @author Him188moe - */ -private object HexComparator { - - private val RED = "\u001b[31m" - private val GREEN = "\u001b[33m" - private val UNKNOWN_COLOR = "\u001b[30m" - private val BLUE = "\u001b[34m" - - @Suppress("unused") - private class ConstMatcher constructor(hex: String) { - private val matches = linkedSetOf<Match>() - - fun getMatchedConstName(hexNumber: Int): String? { - for (match in this.matches) { - if (match.range.contains(hexNumber)) { - return match.constName - } - } - return null - } - - private class Match internal constructor(val range: IntRange, val constName: String) - - init { - CONST_FIELDS.forEach { (name, value) -> - for (match in match(hex, value)) { - matches.add(Match(match, name)) - } - } - } - - companion object { - val CONST_FIELDS: MutableSet<NamedHexElement> = linkedSetOf<NamedHexElement>().apply { initConstFileds() } - } - - private fun match(hex: String, value: String): Set<IntRange> { - val constValue: String - try { - constValue = value.trim { it <= ' ' } - if (constValue.length / 3 <= 3) {//Minimum numbers of const hex bytes - return linkedSetOf() - } - } catch (ignored: ClassCastException) { - return linkedSetOf() - } - - return mutableSetOf<IntRange>().apply { - var index = -1 - index = hex.indexOf(constValue, index + 1) - while (index != -1) { - add(IntRange(index / 3, (index + constValue.length) / 3)) - - index = hex.indexOf(constValue, index + 1) - } - } - } - } - - private fun buildConstNameChain(length: Int, constMatcher: ConstMatcher, constNameBuilder: StringBuilder) { - //System.out.println(constMatcher.matches); - var i = 0 - while (i < length) { - constNameBuilder.append(" ") - val match = constMatcher.getMatchedConstName(i / 4) - if (match != null) { - var appendedNameLength = match.length - constNameBuilder.append(match) - while (match == constMatcher.getMatchedConstName(i++ / 4)) { - if (appendedNameLength-- < 0) { - constNameBuilder.append(" ") - } - } - - constNameBuilder.append(" ".repeat(match.length % 4)) - } - i++ - } - } - - fun compare(hex1s: String, hex2s: String): String { - val builder = StringBuilder() - - val hex1 = hex1s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val hex2 = hex2s.trim { it <= ' ' }.replace("\n", "").split(" ".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - val constMatcher1 = ConstMatcher(hex1s) - val constMatcher2 = ConstMatcher(hex2s) - - if (hex1.size == hex2.size) { - builder.append(GREEN).append("长度一致:").append(hex1.size) - } else { - builder.append(RED).append("长度不一致").append(hex1.size).append("/").append(hex2.size) - } - - - val numberLine = StringBuilder() - val hex1ConstName = StringBuilder() - val hex1b = StringBuilder() - val hex2b = StringBuilder() - val hex2ConstName = StringBuilder() - var dif = 0 - - val length = max(hex1.size, hex2.size) * 4 - buildConstNameChain(length, constMatcher1, hex1ConstName) - buildConstNameChain(length, constMatcher2, hex2ConstName) - - - for (i in 0 until max(hex1.size, hex2.size)) { - var h1: String? = null - var h2: String? = null - var isDif = false - if (hex1.size <= i) { - h1 = RED + "__" - isDif = true - } else { - val matchedConstName = constMatcher1.getMatchedConstName(i) - if (matchedConstName != null) { - h1 = BLUE + hex1[i] - } - } - if (hex2.size <= i) { - h2 = RED + "__" - isDif = true - } else { - val matchedConstName = constMatcher2.getMatchedConstName(i) - if (matchedConstName != null) { - h2 = BLUE + hex2[i] - } - } - - if (h1 == null && h2 == null) { - h1 = hex1[i] - h2 = hex2[i] - if (h1 == h2) { - h1 = GREEN + h1 - h2 = GREEN + h2 - } else { - h1 = RED + h1 - h2 = RED + h2 - isDif = true - } - } else { - if (h1 == null) { - h1 = RED + hex1[i] - } - if (h2 == null) { - h2 = RED + hex2[i] - } - } - - numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ") - hex1b.append(" ").append(h1).append(" ") - hex2b.append(" ").append(h2).append(" ") - if (isDif) { - ++dif - } - - //doConstReplacement(hex1b); - //doConstReplacement(hex2b); - } - - return builder.append(" ").append(dif).append(" 个不同").append("\n") - .append(numberLine).append("\n") - .append(hex1ConstName).append("\n") - .append(hex1b).append("\n") - .append(hex2b).append("\n") - .append(hex2ConstName).append("\n") - .toString() - - - } - - fun String.colorize(ignoreUntilFirstConst: Boolean = false): String { - val builder = StringBuilder() - - val hex = trim { it <= ' ' }.replace("\n", "").split(" ").dropLastWhile { it.isEmpty() }.toTypedArray() - val constMatcher1 = ConstMatcher(this) - - val numberLine = StringBuilder() - val hex1ConstName = StringBuilder() - val hex1b = StringBuilder() - - buildConstNameChain(length, constMatcher1, hex1ConstName) - - var firstConst: String? = null - var constNameOffset = 0//已经因为还没到第一个const跳过了几个char - for (i in hex.indices) { - var h1: String? = null - - val matchedConstName = constMatcher1.getMatchedConstName(i) - if (matchedConstName != null) { - firstConst = firstConst ?: matchedConstName - h1 = BLUE + hex[i] - } - - if (!ignoreUntilFirstConst || firstConst != null) {//有过任意一个 Const - if (h1 == null) { - h1 = GREEN + hex[i] - } - numberLine.append(UNKNOWN_COLOR).append(getFixedNumber(i)).append(" ") - hex1b.append(" ").append(h1).append(" ") - } else { - constNameOffset++ - } - } - - return builder.append("\n") - .append(numberLine).append("\n") - .append(if (firstConst == null) hex1ConstName else { - with(hex1ConstName) { - val index = indexOf(firstConst) - if (index == -1) toString() else " " + substring(index, length) - } - }).append("\n") - .append(hex1b).append("\n") - .toString() - } - - - private fun getFixedNumber(number: Int): String { - if (number < 10) { - return "00$number" - } - return if (number < 100) { - "0$number" - } else number.toString() - } - -} \ No newline at end of file diff --git a/mirai-debug/src/main/kotlin/test/DelegateTest.kt b/mirai-debug/src/main/kotlin/test/DelegateTest.kt deleted file mode 100644 index a1267f32f..000000000 --- a/mirai-debug/src/main/kotlin/test/DelegateTest.kt +++ /dev/null @@ -1,35 +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 test - -import kotlin.reflect.KProperty - - -data class Info( - var value: Int -) { - operator fun getValue(c: C, property: KProperty<*>): Int { - return value - } -} - -class C(var info: Info) { - val value by info -} - -fun main() { - val info = Info(1) - val c = C(info) - println(c.value) //1 - info.value = 2 - println(c.value) //2 - c.info = Info(3) - println(c.value) //2 -} \ No newline at end of file diff --git a/mirai-debug/src/main/kotlin/test/JaptTest.kt b/mirai-debug/src/main/kotlin/test/JaptTest.kt deleted file mode 100644 index 32cd4b149..000000000 --- a/mirai-debug/src/main/kotlin/test/JaptTest.kt +++ /dev/null @@ -1,18 +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 test - -@Suppress("RedundantSuspendModifier") -suspend fun suspendPrintln(arg: String) = println(arg) - -suspend fun main() { - suspendPrintln("Hello") - suspendPrintln(" World!") -} \ No newline at end of file diff --git a/mirai-debug/src/test/java/jce/jce/HexUtil.java b/mirai-debug/src/test/java/jce/jce/HexUtil.java deleted file mode 100644 index 8a3e9b783..000000000 --- a/mirai-debug/src/test/java/jce/jce/HexUtil.java +++ /dev/null @@ -1,79 +0,0 @@ -package jce.jce; - -import java.io.UnsupportedEncodingException; - -public class HexUtil { - private static final char[] digits = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; - public static final byte[] emptybytes = new byte[0]; - - public static String byte2HexStr(byte var0) { - char var1 = digits[var0 & 15]; - var0 = (byte) (var0 >>> 4); - return new String(new char[]{digits[var0 & 15], var1}); - } - - public static String bytes2HexStr(byte[] var0) { - if (var0 != null && var0.length != 0) { - char[] var3 = new char[var0.length * 2]; - - for (int var1 = 0; var1 < var0.length; ++var1) { - byte var2 = var0[var1]; - var3[var1 * 2 + 1] = digits[var2 & 15]; - var2 = (byte) (var2 >>> 4); - var3[var1 * 2 + 0] = digits[var2 & 15]; - } - - return new String(var3); - } else { - return null; - } - } - - public static byte char2Byte(char var0) { - if (var0 >= '0' && var0 <= '9') { - return (byte) (var0 - 48); - } else if (var0 >= 'a' && var0 <= 'f') { - return (byte) (var0 - 97 + 10); - } else { - return var0 >= 'A' && var0 <= 'F' ? (byte) (var0 - 65 + 10) : 0; - } - } - - public static byte hexStr2Byte(String var0) { - byte var2 = 0; - byte var1 = var2; - if (var0 != null) { - var1 = var2; - if (var0.length() == 1) { - var1 = char2Byte(var0.charAt(0)); - } - } - - return var1; - } - - public static byte[] hexStr2Bytes(String var0) { - if (var0 != null && !var0.equals("")) { - byte[] var4 = new byte[var0.length() / 2]; - - for (int var3 = 0; var3 < var4.length; ++var3) { - char var1 = var0.charAt(var3 * 2); - char var2 = var0.charAt(var3 * 2 + 1); - var4[var3] = (byte) (char2Byte(var1) * 16 + char2Byte(var2)); - } - - return var4; - } else { - return emptybytes; - } - } - - public static void main(String[] var0) { - try { - byte[] var2 = "Hello WebSocket World?".getBytes("gbk"); - System.out.println(bytes2HexStr(var2)); - } catch (UnsupportedEncodingException var1) { - var1.printStackTrace(); - } - } -} diff --git a/mirai-debug/src/test/java/jce/jce/JceDecodeException.java b/mirai-debug/src/test/java/jce/jce/JceDecodeException.java deleted file mode 100644 index 49c4bd5b7..000000000 --- a/mirai-debug/src/test/java/jce/jce/JceDecodeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package jce.jce; - -public class JceDecodeException extends RuntimeException { - public JceDecodeException(String var1) { - super(var1); - } -} diff --git a/mirai-debug/src/test/java/jce/jce/JceEncodeException.java b/mirai-debug/src/test/java/jce/jce/JceEncodeException.java deleted file mode 100644 index 40c7a5e11..000000000 --- a/mirai-debug/src/test/java/jce/jce/JceEncodeException.java +++ /dev/null @@ -1,7 +0,0 @@ -package jce.jce; - -public class JceEncodeException extends RuntimeException { - public JceEncodeException(String var1) { - super(var1); - } -} diff --git a/mirai-debug/src/test/java/jce/jce/JceInputStream$HeadData.java b/mirai-debug/src/test/java/jce/jce/JceInputStream$HeadData.java deleted file mode 100644 index df97f8166..000000000 --- a/mirai-debug/src/test/java/jce/jce/JceInputStream$HeadData.java +++ /dev/null @@ -1,11 +0,0 @@ -package jce.jce; - -public class JceInputStream$HeadData { - public int tag; - public byte type; - - public void clear() { - this.type = 0; - this.tag = 0; - } -} diff --git a/mirai-debug/src/test/java/jce/jce/JceInputStream.java b/mirai-debug/src/test/java/jce/jce/JceInputStream.java deleted file mode 100644 index 64b89c17b..000000000 --- a/mirai-debug/src/test/java/jce/jce/JceInputStream.java +++ /dev/null @@ -1,1010 +0,0 @@ -package jce.jce; - -import java.io.UnsupportedEncodingException; -import java.lang.reflect.Array; -import java.nio.BufferUnderflowException; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -public final class JceInputStream { - // $FF: renamed from: bs java.nio.ByteBuffer - private ByteBuffer buffer; - protected String sServerEncoding = "GBK"; - - public JceInputStream() { - } - - public JceInputStream(ByteBuffer var1) { - this.buffer = var1; - } - - public JceInputStream(byte[] var1) { - this.buffer = ByteBuffer.wrap(var1); - } - - public JceInputStream(byte[] var1, int var2) { - this.buffer = ByteBuffer.wrap(var1); - this.buffer.position(var2); - } - - public static void main(String[] var0) { - } - - private int peakHead(JceInputStream$HeadData var1) { - return readHead(var1, this.buffer.duplicate()); - } - - private <T> T[] readArrayImpl(T var1, int var2, boolean var3) { - Object[] var7; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var5 = new JceInputStream$HeadData(); - this.readHead(var5); - if (var5.type == 9) { - int var4 = this.read(0, 0, true); - if (var4 < 0) { - throw new JceDecodeException("size invalid: " + var4); - } else { - Object[] var6 = (Object[]) Array.newInstance(var1.getClass(), var4); - var2 = 0; - - while (true) { - var7 = var6; - if (var2 >= var4) { - return (T[]) var7; - } - - var6[var2] = this.read(var1, 0, true); - ++var2; - } - } - } - throw new JceDecodeException("type mismatch."); - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - var7 = null; - return (T[]) var7; - } - } - - public static int readHead(JceInputStream$HeadData var0, ByteBuffer var1) { - byte var2 = var1.get(); - var0.type = (byte) (var2 & 15); - var0.tag = (var2 & 240) >> 4; - if (var0.tag == 15) { - var0.tag = var1.get() & 255; - return 2; - } else { - return 1; - } - } - - private <K, V> Map<K, V> readMap(Map<K, V> var1, Map<K, V> var2, int var3, boolean var4) { - Map<K, V> var8; - if (var2 != null && !var2.isEmpty()) { - Entry<K, V> var9 = var2.entrySet().iterator().next(); - K var6 = var9.getKey(); - V var7 = var9.getValue(); - if (this.skipToTag(var3)) { - JceInputStream$HeadData var10 = new JceInputStream$HeadData(); - this.readHead(var10); - if (var10.type == 8) { - int var5 = this.read(0, 0, true); - if (var5 < 0) { - throw new JceDecodeException("size invalid: " + var5); - } - - var3 = 0; - - while (true) { - var8 = var1; - if (var3 >= var5) { - return var8; - } - - var1.put((K) this.read(var6, 0, true), (V) this.read(var7, 1, true)); - ++var3; - } - } - throw new JceDecodeException("type mismatch."); - } else { - var8 = var1; - if (var4) { - throw new JceDecodeException("require field not exist."); - } - } - } else { - var8 = new HashMap<>(); - } - - return var8; - } - - private void skip(int var1) { - this.buffer.position(this.buffer.position() + var1); - } - - private void skipField() { - JceInputStream$HeadData var1 = new JceInputStream$HeadData(); - this.readHead(var1); - this.skipField(var1.type); - } - - private void skipField(byte var1) { - byte var3 = 0; - byte var2 = 0; - int var5; - switch (var1) { - case 0: - this.skip(1); - break; - case 1: - this.skip(2); - return; - case 2: - this.skip(4); - return; - case 3: - this.skip(8); - return; - case 4: - this.skip(4); - return; - case 5: - this.skip(8); - return; - case 6: - byte var7 = this.buffer.get(); - var5 = var7; - if (var7 < 0) { - var5 = var7 + 256; - } - - this.skip(var5); - return; - case 7: - this.skip(this.buffer.getInt()); - return; - case 8: - int var8 = this.read(0, 0, true); - - for (var5 = var2; var5 < var8 * 2; ++var5) { - this.skipField(); - } - - return; - case 9: - int var6 = this.read(0, 0, true); - - for (var5 = var3; var5 < var6; ++var5) { - this.skipField(); - } - - return; - case 10: - this.skipToStructEnd(); - return; - case 11: - case 12: - break; - case 13: - JceInputStream$HeadData var4 = new JceInputStream$HeadData(); - this.readHead(var4); - if (var4.type != 0) { - throw new JceDecodeException("skipField with invalid type, type value: " + var1 + ", " + var4.type); - } - - this.skip(this.read(0, 0, true)); - return; - default: - throw new JceDecodeException("invalid type."); - } - - } - - public JceStruct directRead(JceStruct var1, int var2, boolean var3) { - JceInputStream$HeadData var4 = null; - if (this.skipToTag(var2)) { - try { - var1 = var1.newInit(); - } catch (Exception var5) { - throw new JceDecodeException(var5.getMessage()); - } - - var4 = new JceInputStream$HeadData(); - this.readHead(var4); - if (var4.type != 10) { - throw new JceDecodeException("type mismatch."); - } - - var1.readFrom(this); - this.skipToStructEnd(); - } else { - var1 = null; - if (var3) { - throw new JceDecodeException("require field not exist."); - } - } - - return var1; - } - - public ByteBuffer getBs() { - return this.buffer; - } - - public byte read(byte var1, int var2, boolean var3) { - if (this.skipToTag(var2)) { - JceInputStream$HeadData var4 = new JceInputStream$HeadData(); - this.readHead(var4); - switch (var4.type) { - case 0: - return this.buffer.get(); - case 12: - return 0; - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public double read(double var1, int var3, boolean var4) { - if (this.skipToTag(var3)) { - JceInputStream$HeadData var5 = new JceInputStream$HeadData(); - this.readHead(var5); - switch (var5.type) { - case 4: - return this.buffer.getFloat(); - case 5: - return this.buffer.getDouble(); - case 12: - return 0.0D; - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var4) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public float read(float var1, int var2, boolean var3) { - if (this.skipToTag(var2)) { - JceInputStream$HeadData var4 = new JceInputStream$HeadData(); - this.readHead(var4); - switch (var4.type) { - case 4: - return this.buffer.getFloat(); - case 12: - return 0.0F; - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public int read(int var1, int var2, boolean var3) { - if (this.skipToTag(var2)) { - JceInputStream$HeadData var4 = new JceInputStream$HeadData(); - this.readHead(var4); - switch (var4.type) { - case 0: - return this.buffer.get(); - case 1: - return this.buffer.getShort(); - case 2: - return this.buffer.getInt(); - case 12: - return 0; - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public long read(long var1, int var3, boolean var4) { - if (this.skipToTag(var3)) { - JceInputStream$HeadData var5 = new JceInputStream$HeadData(); - this.readHead(var5); - switch (var5.type) { - case 0: - return this.buffer.get(); - case 1: - return this.buffer.getShort(); - case 2: - return this.buffer.getInt(); - case 3: - return this.buffer.getLong(); - case 12: - return 0L; - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var4) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public JceStruct read(JceStruct var1, int var2, boolean var3) { - JceInputStream$HeadData var4 = null; - if (this.skipToTag(var2)) { - try { - var1 = var1.getClass().getConstructor().newInstance(); - } catch (Exception var5) { - throw new JceDecodeException(var5.getMessage()); - } - - System.out.println(var1.toString()); - - var4 = new JceInputStream$HeadData(); - this.readHead(var4); - if (var4.type != 10) { - throw new JceDecodeException("type mismatch."); - } - - var1.readFrom(this); - this.skipToStructEnd(); - } else { - var1 = null; - if (var3) { - throw new JceDecodeException("require field not exist."); - } - } - - return var1; - } - - public <T> Object read(T var1, int var2, boolean var3) { - if (var1 instanceof Byte) { - return this.read((byte) 0, var2, var3); - } else if (var1 instanceof Boolean) { - return this.read(false, var2, var3); - } else if (var1 instanceof Short) { - return this.read((short) 0, var2, var3); - } else if (var1 instanceof Integer) { - return this.read(0, var2, var3); - } else if (var1 instanceof Long) { - return this.read(0L, var2, var3); - } else if (var1 instanceof Float) { - return this.read(0.0F, var2, var3); - } else if (var1 instanceof Double) { - return this.read(0.0D, var2, var3); - } else if (var1 instanceof String) { - return this.readString(var2, var3); - } else if (var1 instanceof Map) { - return this.readMap((Map) var1, var2, var3); - } else if (var1 instanceof List) { - return this.readArray((List) var1, var2, var3); - } else if (var1 instanceof JceStruct) { - return this.read((JceStruct) var1, var2, var3); - } else if (var1.getClass().isArray()) { - if (!(var1 instanceof byte[]) && !(var1 instanceof Byte[])) { - if (var1 instanceof boolean[]) { - return this.read((boolean[]) null, var2, var3); - } else if (var1 instanceof short[]) { - return this.read((short[]) null, var2, var3); - } else if (var1 instanceof int[]) { - return this.read((int[]) null, var2, var3); - } else if (var1 instanceof long[]) { - return this.read((long[]) null, var2, var3); - } else if (var1 instanceof float[]) { - return this.read((float[]) null, var2, var3); - } else { - return var1 instanceof double[] ? this.read((double[]) null, var2, var3) : this.readArray((Object[]) var1, var2, var3); - } - } else { - return this.read((byte[]) null, var2, var3); - } - } else { - throw new JceDecodeException("read object error: unsupport type."); - } - } - - public String read(String var1, int var2, boolean var3) { - if (this.skipToTag(var2)) { - JceInputStream$HeadData var8 = new JceInputStream$HeadData(); - this.readHead(var8); - String var5; - byte[] var9; - switch (var8.type) { - case 6: - byte var4 = this.buffer.get(); - var2 = var4; - if (var4 < 0) { - var2 = var4 + 256; - } - - var9 = new byte[var2]; - this.buffer.get(var9); - - try { - var5 = new String(var9, this.sServerEncoding); - return var5; - } catch (UnsupportedEncodingException var7) { - return new String(var9); - } - case 7: - var2 = this.buffer.getInt(); - if (var2 <= 104857600 && var2 >= 0 && var2 <= this.buffer.capacity()) { - var9 = new byte[var2]; - this.buffer.get(var9); - - try { - var5 = new String(var9, this.sServerEncoding); - return var5; - } catch (UnsupportedEncodingException var6) { - return new String(var9); - } - } - - throw new JceDecodeException("String too long: " + var2); - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public short read(short var1, int var2, boolean var3) { - if (this.skipToTag(var2)) { - JceInputStream$HeadData var4 = new JceInputStream$HeadData(); - this.readHead(var4); - switch (var4.type) { - case 0: - return this.buffer.get(); - case 1: - return this.buffer.getShort(); - case 12: - return 0; - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public boolean read(boolean var1, int var2, boolean var3) { - var1 = this.read((byte) 0, var2, var3) != 0; - - return var1; - } - - public byte[] read(byte[] var1, int var2, boolean var3) { - var1 = null; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var6 = new JceInputStream$HeadData(); - this.readHead(var6); - int var4; - switch (var6.type) { - case 9: - var4 = this.read(0, 0, true); - if (var4 >= 0 && var4 <= this.buffer.capacity()) { - byte[] var7 = new byte[var4]; - var2 = 0; - - while (true) { - var1 = var7; - if (var2 >= var4) { - return var1; - } - - var7[var2] = this.read(var7[0], 0, true); - ++var2; - } - } - - throw new JceDecodeException("size invalid: " + var4); - case 13: - JceInputStream$HeadData var5 = new JceInputStream$HeadData(); - this.readHead(var5); - if (var5.type != 0) { - throw new JceDecodeException("type mismatch, tag: " + var2 + ", type: " + var6.type + ", " + var5.type); - } - - var4 = this.read(0, 0, true); - if (var4 < 0 || var4 > this.buffer.capacity()) { - throw new JceDecodeException("invalid size, tag: " + var2 + ", type: " + var6.type + ", " + var5.type + ", size: " + var4); - } - - var1 = new byte[var4]; - this.buffer.get(var1); - break; - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } - - return var1; - } - - public double[] read(double[] var1, int var2, boolean var3) { - var1 = null; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var6 = new JceInputStream$HeadData(); - this.readHead(var6); - if (var6.type == 9) { - int var4 = this.read(0, 0, true); - if (var4 < 0) { - throw new JceDecodeException("size invalid: " + var4); - } else { - double[] var5 = new double[var4]; - var2 = 0; - - while (true) { - var1 = var5; - if (var2 >= var4) { - return var1; - } - - var5[var2] = this.read(var5[0], 0, true); - ++var2; - } - } - } - throw new JceDecodeException("type mismatch."); - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public float[] read(float[] var1, int var2, boolean var3) { - var1 = null; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var6 = new JceInputStream$HeadData(); - this.readHead(var6); - if (var6.type == 9) { - int var4 = this.read(0, 0, true); - if (var4 < 0) { - throw new JceDecodeException("size invalid: " + var4); - } else { - float[] var5 = new float[var4]; - var2 = 0; - - while (true) { - var1 = var5; - if (var2 >= var4) { - return var1; - } - - var5[var2] = this.read(var5[0], 0, true); - ++var2; - } - } - } - throw new JceDecodeException("type mismatch."); - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public int[] read(int[] var1, int var2, boolean var3) { - var1 = null; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var6 = new JceInputStream$HeadData(); - this.readHead(var6); - if (var6.type == 9) { - int var4 = this.read(0, 0, true); - if (var4 < 0) { - throw new JceDecodeException("size invalid: " + var4); - } else { - int[] var5 = new int[var4]; - var2 = 0; - - while (true) { - var1 = var5; - if (var2 >= var4) { - return var1; - } - - var5[var2] = this.read(var5[0], 0, true); - ++var2; - } - } - } - throw new JceDecodeException("type mismatch."); - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public long[] read(long[] var1, int var2, boolean var3) { - var1 = null; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var6 = new JceInputStream$HeadData(); - this.readHead(var6); - if (var6.type == 9) { - int var4 = this.read(0, 0, true); - if (var4 < 0) { - throw new JceDecodeException("size invalid: " + var4); - } else { - long[] var5 = new long[var4]; - var2 = 0; - - while (true) { - var1 = var5; - if (var2 >= var4) { - return var1; - } - - var5[var2] = this.read(var5[0], 0, true); - ++var2; - } - } - } - throw new JceDecodeException("type mismatch."); - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public JceStruct[] read(JceStruct[] var1, int var2, boolean var3) { - return (JceStruct[]) this.readArray((Object[]) var1, var2, var3); - } - - public String[] read(String[] var1, int var2, boolean var3) { - return (String[]) this.readArray((Object[]) var1, var2, var3); - } - - public short[] read(short[] var1, int var2, boolean var3) { - var1 = null; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var6 = new JceInputStream$HeadData(); - this.readHead(var6); - if (var6.type == 9) { - int var4 = this.read(0, 0, true); - if (var4 < 0) { - throw new JceDecodeException("size invalid: " + var4); - } else { - short[] var5 = new short[var4]; - var2 = 0; - - while (true) { - var1 = var5; - if (var2 >= var4) { - return var1; - } - - var5[var2] = this.read(var5[0], 0, true); - ++var2; - } - } - } - throw new JceDecodeException("type mismatch."); - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public boolean[] read(boolean[] var1, int var2, boolean var3) { - var1 = null; - if (this.skipToTag(var2)) { - JceInputStream$HeadData var6 = new JceInputStream$HeadData(); - this.readHead(var6); - if (var6.type == 9) { - int var4 = this.read(0, 0, true); - if (var4 < 0) { - throw new JceDecodeException("size invalid: " + var4); - } else { - boolean[] var5 = new boolean[var4]; - var2 = 0; - - while (true) { - var1 = var5; - if (var2 >= var4) { - return var1; - } - - var5[var2] = this.read(var5[0], 0, true); - ++var2; - } - } - } - throw new JceDecodeException("type mismatch."); - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public <T> List<T> readArray(List<T> var1, int var2, boolean var3) { - byte var4 = 0; - if (var1 != null && !var1.isEmpty()) { - Object[] var6 = this.readArrayImpl(var1.get(0), var2, var3); - if (var6 == null) { - return null; - } else { - ArrayList var5 = new ArrayList(); - - for (var2 = var4; var2 < var6.length; ++var2) { - var5.add(var6[var2]); - } - - return var5; - } - } else { - return new ArrayList<>(); - } - } - - public <T> T[] readArray(T[] var1, int var2, boolean var3) { - if (var1 != null && var1.length != 0) { - return this.readArrayImpl(var1[0], var2, var3); - } else { - throw new JceDecodeException("unable to get type of key and value."); - } - } - - public String readByteString(String var1, int var2, boolean var3) { - if (this.skipToTag(var2)) { - JceInputStream$HeadData var5 = new JceInputStream$HeadData(); - this.readHead(var5); - byte[] var6; - switch (var5.type) { - case 6: - byte var4 = this.buffer.get(); - var2 = var4; - if (var4 < 0) { - var2 = var4 + 256; - } - - var6 = new byte[var2]; - this.buffer.get(var6); - return HexUtil.bytes2HexStr(var6); - case 7: - var2 = this.buffer.getInt(); - if (var2 <= 104857600 && var2 >= 0 && var2 <= this.buffer.capacity()) { - var6 = new byte[var2]; - this.buffer.get(var6); - return HexUtil.bytes2HexStr(var6); - } - - throw new JceDecodeException("String too long: " + var2); - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var3) { - throw new JceDecodeException("require field not exist."); - } else { - return var1; - } - } - - public void readHead(JceInputStream$HeadData var1) { - readHead(var1, this.buffer); - } - - public List<java.io.Serializable> readList(int var1, boolean var2) { - ArrayList<java.io.Serializable> var6 = new ArrayList<java.io.Serializable>(); - if (this.skipToTag(var1)) { - JceInputStream$HeadData var7 = new JceInputStream$HeadData(); - this.readHead(var7); - if (var7.type == 9) { - int var5 = this.read(0, 0, true); - if (var5 < 0) { - throw new JceDecodeException("size invalid: " + var5); - } - - var1 = 0; - - for (; var1 < var5; ++var1) { - var7 = new JceInputStream$HeadData(); - this.readHead(var7); - switch (var7.type) { - case 0: - this.skip(1); - break; - case 1: - this.skip(2); - break; - case 2: - this.skip(4); - break; - case 3: - this.skip(8); - break; - case 4: - this.skip(4); - break; - case 5: - this.skip(8); - break; - case 6: - byte var4 = this.buffer.get(); - int var3 = var4; - if (var4 < 0) { - var3 = var4 + 256; - } - - this.skip(var3); - break; - case 7: - this.skip(this.buffer.getInt()); - case 8: - case 9: - break; - case 10: - try { - JceStruct var9 = (JceStruct) Class.forName(JceStruct.class.getName()).getConstructor().newInstance(); - var9.readFrom(this); - this.skipToStructEnd(); - var6.add(var9); - break; - } catch (Exception var8) { - var8.printStackTrace(); - throw new JceDecodeException("type mismatch." + var8); - } - case 11: - default: - throw new JceDecodeException("type mismatch."); - case 12: - var6.add(0); - } - } - } else { - throw new JceDecodeException("type mismatch."); - } - } else if (var2) { - throw new JceDecodeException("require field not exist."); - } - - return var6; - } - - public <K, V> HashMap<K, V> readMap(Map<K, V> var1, int var2, boolean var3) { - return (HashMap<K, V>) this.readMap(new HashMap<K, V>(), var1, var2, var3); - } - - public String readString(int var1, boolean var2) { - String var4 = null; - if (this.skipToTag(var1)) { - JceInputStream$HeadData var8 = new JceInputStream$HeadData(); - this.readHead(var8); - switch (var8.type) { - case 6: - byte var3 = this.buffer.get(); - var1 = var3; - if (var3 < 0) { - var1 = var3 + 256; - } - - byte[] var10 = new byte[var1]; - this.buffer.get(var10); - - try { - var4 = new String(var10, this.sServerEncoding); - break; - } catch (UnsupportedEncodingException var7) { - return new String(var10); - } - case 7: - var1 = this.buffer.getInt(); - if (var1 <= 104857600 && var1 >= 0 && var1 <= this.buffer.capacity()) { - byte[] var9 = new byte[var1]; - this.buffer.get(var9); - - try { - String var5 = new String(var9, this.sServerEncoding); - return var5; - } catch (UnsupportedEncodingException var6) { - return new String(var9); - } - } - - throw new JceDecodeException("String too long: " + var1); - default: - throw new JceDecodeException("type mismatch."); - } - } else if (var2) { - throw new JceDecodeException("require field not exist."); - } - - return var4; - } - - public Map<String, String> readStringMap(int var1, boolean var2) { - HashMap<String, String> var4 = new HashMap<>(); - if (this.skipToTag(var1)) { - JceInputStream$HeadData var5 = new JceInputStream$HeadData(); - this.readHead(var5); - if (var5.type == 8) { - int var3 = this.read(0, 0, true); - if (var3 < 0) { - throw new JceDecodeException("size invalid: " + var3); - } - - for (var1 = 0; var1 < var3; ++var1) { - var4.put(this.readString(0, true), this.readString(1, true)); - } - } else { - throw new JceDecodeException("type mismatch."); - } - } else if (var2) { - throw new JceDecodeException("require field not exist."); - } - - return var4; - } - - public int setServerEncoding(String var1) { - this.sServerEncoding = var1; - return 0; - } - - public void skipToStructEnd() { - JceInputStream$HeadData var1 = new JceInputStream$HeadData(); - - do { - this.readHead(var1); - this.skipField(var1.type); - } while (var1.type != 11); - - } - - public boolean skipToTag(int n2) { - try { - JceInputStream$HeadData jceInputStream$HeadData = new JceInputStream$HeadData(); - do { - int n3 = this.peakHead(jceInputStream$HeadData); - if (jceInputStream$HeadData.type == 11) { - return false; - } - if (n2 <= jceInputStream$HeadData.tag) { - return n2 == jceInputStream$HeadData.tag; - } - this.skip(n3); - this.skipField(jceInputStream$HeadData.type); - } while (true); - } catch (JceDecodeException jceDecodeException) { - return false; - } catch (BufferUnderflowException bufferUnderflowException) { - // empty catch block - } - return false; - } - - public void warp(byte[] var1) { - this.wrap(var1); - } - - public void wrap(byte[] var1) { - this.buffer = ByteBuffer.wrap(var1); - } -} diff --git a/mirai-debug/src/test/java/jce/jce/JceOutputStream.java b/mirai-debug/src/test/java/jce/jce/JceOutputStream.java deleted file mode 100644 index 7b2179271..000000000 --- a/mirai-debug/src/test/java/jce/jce/JceOutputStream.java +++ /dev/null @@ -1,430 +0,0 @@ -package jce.jce; - -import java.io.UnsupportedEncodingException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -public class JceOutputStream { - // $FF: renamed from: bs java.nio.ByteBuffer - private ByteBuffer field_80728; - private OnIllegalArgumentException exceptionHandler; - protected String sServerEncoding; - - public JceOutputStream() { - this(128); - } - - public JceOutputStream(int var1) { - this.sServerEncoding = "GBK"; - this.field_80728 = ByteBuffer.allocate(var1); - } - - public JceOutputStream(ByteBuffer var1) { - this.sServerEncoding = "GBK"; - this.field_80728 = var1; - } - - public static void main(String[] var0) { - JceOutputStream var2 = new JceOutputStream(); - var2.write(1311768467283714885L, 0); - ByteBuffer var1 = var2.getByteBuffer(); - System.out.println(HexUtil.bytes2HexStr(var1.array())); - System.out.println(Arrays.toString(var2.toByteArray())); - } - - private void writeArray(Object[] var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - this.write(var1.length, 0); - int var3 = var1.length; - - for (var2 = 0; var2 < var3; ++var2) { - this.write(var1[var2], 0); - } - - } - - public ByteBuffer getByteBuffer() { - return this.field_80728; - } - - public OnIllegalArgumentException getExceptionHandler() { - return this.exceptionHandler; - } - - public void reserve(int var1) { - if (this.field_80728.remaining() < var1) { - int var2 = (this.field_80728.capacity() + var1) * 2; - - ByteBuffer var3; - try { - var3 = ByteBuffer.allocate(var2); - var3.put(this.field_80728.array(), 0, this.field_80728.position()); - } catch (IllegalArgumentException var4) { - if (this.exceptionHandler != null) { - this.exceptionHandler.onException(var4, this.field_80728, var1, var2); - } - - throw var4; - } - - this.field_80728 = var3; - } - - } - - public void setExceptionHandler(OnIllegalArgumentException var1) { - this.exceptionHandler = var1; - } - - public int setServerEncoding(String var1) { - this.sServerEncoding = var1; - return 0; - } - - public byte[] toByteArray() { - byte[] var1 = new byte[this.field_80728.position()]; - System.arraycopy(this.field_80728.array(), 0, var1, 0, this.field_80728.position()); - return var1; - } - - public void write(byte var1, int var2) { - this.reserve(3); - if (var1 == 0) { - this.writeHead((byte) 12, var2); - } else { - this.writeHead((byte) 0, var2); - this.field_80728.put(var1); - } - } - - public void write(double var1, int var3) { - this.reserve(10); - this.writeHead((byte) 5, var3); - this.field_80728.putDouble(var1); - } - - public void write(float var1, int var2) { - this.reserve(6); - this.writeHead((byte) 4, var2); - this.field_80728.putFloat(var1); - } - - public void write(int var1, int var2) { - this.reserve(6); - if (var1 >= -32768 && var1 <= 32767) { - this.write((short) var1, var2); - } else { - this.writeHead((byte) 2, var2); - this.field_80728.putInt(var1); - } - } - - public void write(long var1, int id) { - this.reserve(10); - if (var1 >= -2147483648L && var1 <= 2147483647L) { - this.write((int) var1, id); - } else { - this.writeHead((byte) 3, id); - this.field_80728.putLong(var1); - } - } - - public void write(JceStruct var1, int var2) { - this.reserve(2); - this.writeHead((byte) 10, var2); - var1.writeTo(this); - this.reserve(2); - this.writeHead((byte) 11, 0); - } - - public void write(Boolean var1, int var2) { - this.write((boolean) var1, var2); - } - - public void write(Byte var1, int var2) { - this.write((byte) var1, var2); - } - - public void write(Double var1, int var2) { - this.write((double) var1, var2); - } - - public void write(Float var1, int var2) { - this.write((float) var1, var2); - } - - public void write(Integer var1, int var2) { - this.write((int) var1, var2); - } - - public void write(Long var1, int var2) { - this.write((long) var1, var2); - } - - public void write(Object var1, int var2) { - if (var1 instanceof Byte) { - this.write((Byte) var1, var2); - } else if (var1 instanceof Boolean) { - this.write((Boolean) var1, var2); - } else if (var1 instanceof Short) { - this.write((Short) var1, var2); - } else if (var1 instanceof Integer) { - this.write((Integer) var1, var2); - } else if (var1 instanceof Long) { - this.write((Long) var1, var2); - } else if (var1 instanceof Float) { - this.write((Float) var1, var2); - } else if (var1 instanceof Double) { - this.write((Double) var1, var2); - } else if (var1 instanceof String) { - this.write((String) var1, var2); - } else if (var1 instanceof Map) { - this.write((Map) var1, var2); - } else if (var1 instanceof List) { - this.write((List) var1, var2); - } else if (var1 instanceof JceStruct) { - this.write((JceStruct) var1, var2); - } else if (var1 instanceof byte[]) { - this.write((byte[]) var1, var2); - } else if (var1 instanceof boolean[]) { - this.write((boolean[]) var1, var2); - } else if (var1 instanceof short[]) { - this.write((short[]) var1, var2); - } else if (var1 instanceof int[]) { - this.write((int[]) var1, var2); - } else if (var1 instanceof long[]) { - this.write((long[]) var1, var2); - } else if (var1 instanceof float[]) { - this.write((float[]) var1, var2); - } else if (var1 instanceof double[]) { - this.write((double[]) var1, var2); - } else if (var1.getClass().isArray()) { - this.writeArray((Object[]) var1, var2); - } else if (var1 instanceof Collection) { - this.write((Collection) var1, var2); - } else { - throw new JceEncodeException("write object error: unsupport type. " + var1.getClass()); - } - } - - public void write(Short var1, int var2) { - this.write((short) var1, var2); - } - - public void write(String var1, int var2) { - byte[] var5; - label16: - { - byte[] var3; - try { - var3 = var1.getBytes(this.sServerEncoding); - } catch (UnsupportedEncodingException var4) { - var5 = var1.getBytes(); - break label16; - } - - var5 = var3; - } - - this.reserve(var5.length + 10); - if (var5.length > 255) { - this.writeHead((byte) 7, var2); - this.field_80728.putInt(var5.length); - } else { - this.writeHead((byte) 6, var2); - this.field_80728.put((byte) var5.length); - } - this.field_80728.put(var5); - } - - public <T> void write(Collection<T> var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - if (var1 == null) { - var2 = 0; - } else { - var2 = var1.size(); - } - - this.write(var2, 0); - if (var1 != null) { - - for (T t : var1) { - this.write(t, 0); - } - } - - } - - public <K, V> void write(Map<K, V> var1, int var2) { - this.reserve(8); - this.writeHead((byte) 8, var2); - if (var1 == null) { - var2 = 0; - } else { - var2 = var1.size(); - } - - this.write(var2, 0); - if (var1 != null) { - - for (Entry<K, V> kvEntry : var1.entrySet()) { - this.write(((Entry) kvEntry).getKey(), 0); - this.write(((Entry) kvEntry).getValue(), 1); - } - } - - } - - public void write(short var1, int var2) { - this.reserve(4); - if (var1 >= -128 && var1 <= 127) { - this.write((byte) var1, var2); - } else { - this.writeHead((byte) 1, var2); - this.field_80728.putShort(var1); - } - } - - public void write(boolean var1, int var2) { - byte var3; - if (var1) { - var3 = 1; - } else { - var3 = 0; - } - - this.write(var3, var2); - } - - public void write(byte[] var1, int var2) { - this.reserve(var1.length + 8); - this.writeHead((byte) 13, var2); - this.writeHead((byte) 0, 0); - this.write(var1.length, 0); - this.field_80728.put(var1); - } - - public void write(double[] var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - this.write(var1.length, 0); - int var3 = var1.length; - - for (var2 = 0; var2 < var3; ++var2) { - this.write(var1[var2], 0); - } - - } - - public void write(float[] var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - this.write(var1.length, 0); - int var3 = var1.length; - - for (var2 = 0; var2 < var3; ++var2) { - this.write(var1[var2], 0); - } - - } - - public void write(int[] var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - this.write(var1.length, 0); - int var3 = var1.length; - - for (var2 = 0; var2 < var3; ++var2) { - this.write(var1[var2], 0); - } - - } - - public void write(long[] var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - this.write(var1.length, 0); - int var3 = var1.length; - - for (var2 = 0; var2 < var3; ++var2) { - this.write(var1[var2], 0); - } - - } - - public <T> void write(T[] var1, int var2) { - this.writeArray(var1, var2); - } - - public void write(short[] var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - this.write(var1.length, 0); - int var3 = var1.length; - - for (var2 = 0; var2 < var3; ++var2) { - this.write(var1[var2], 0); - } - - } - - public void write(boolean[] var1, int var2) { - this.reserve(8); - this.writeHead((byte) 9, var2); - this.write(var1.length, 0); - int var3 = var1.length; - - for (var2 = 0; var2 < var3; ++var2) { - this.write(var1[var2], 0); - } - - } - - public void writeByteString(String var1, int var2) { - this.reserve(var1.length() + 10); - byte[] var3 = HexUtil.hexStr2Bytes(var1); - if (var3.length > 255) { - this.writeHead((byte) 7, var2); - this.field_80728.putInt(var3.length); - this.field_80728.put(var3); - } else { - this.writeHead((byte) 6, var2); - this.field_80728.put((byte) var3.length); - this.field_80728.put(var3); - } - } - - public void writeHead(byte var1, int tag) { - byte var3; - if (tag < 15) { - var3 = (byte) (tag << 4 | var1); - this.field_80728.put(var3); - } else if (tag < 256) { - var3 = (byte) (var1 | 240); - this.field_80728.put(var3); - this.field_80728.put((byte) tag); - } else { - throw new JceEncodeException("tag is too large: " + tag); - } - } - - public void writeStringByte(String var1, int var2) { - byte[] var3 = HexUtil.hexStr2Bytes(var1); - this.reserve(var3.length + 10); - if (var3.length > 255) { - this.writeHead((byte) 7, var2); - this.field_80728.putInt(var3.length); - this.field_80728.put(var3); - } else { - this.writeHead((byte) 6, var2); - this.field_80728.put((byte) var3.length); - this.field_80728.put(var3); - } - } -} diff --git a/mirai-debug/src/test/java/jce/jce/JceStruct.java b/mirai-debug/src/test/java/jce/jce/JceStruct.java deleted file mode 100644 index 9c32025d6..000000000 --- a/mirai-debug/src/test/java/jce/jce/JceStruct.java +++ /dev/null @@ -1,78 +0,0 @@ -package jce.jce; - -import java.io.Serializable; - -public abstract class JceStruct implements Serializable { - public static final byte BYTE = 0; - public static final byte DOUBLE = 5; - public static final byte FLOAT = 4; - public static final byte INT = 2; - public static final int JCE_MAX_STRING_LENGTH = 104857600; - public static final byte LIST = 9; - public static final byte LONG = 3; - public static final byte MAP = 8; - public static final byte SHORT = 1; - public static final byte SIMPLE_LIST = 13; - public static final byte STRING1 = 6; - public static final byte STRING4 = 7; - public static final byte STRUCT_BEGIN = 10; - public static final byte STRUCT_END = 11; - public static final byte ZERO_TAG = 12; - - public static String toDisplaySimpleString(JceStruct var0) { - if (var0 == null) { - return null; - } else { - StringBuilder var1 = new StringBuilder(); - var0.displaySimple(var1, 0); - return var1.toString(); - } - } - - public boolean containField(String var1) { - return false; - } - - public void display(StringBuilder var1, int var2) { - } - - public void displaySimple(StringBuilder var1, int var2) { - } - - public Object getFieldByName(String var1) { - return null; - } - - public JceStruct newInit() { - return null; - } - - public abstract void readFrom(JceInputStream var1); - - public void recyle() { - } - - public void setFieldByName(String var1, Object var2) { - } - - public byte[] toByteArray() { - JceOutputStream var1 = new JceOutputStream(); - this.writeTo(var1); - return var1.toByteArray(); - } - - public byte[] toByteArray(String var1) { - JceOutputStream var2 = new JceOutputStream(); - var2.setServerEncoding(var1); - this.writeTo(var2); - return var2.toByteArray(); - } - - public String toString() { - StringBuilder var1 = new StringBuilder(); - this.display(var1, 0); - return var1.toString(); - } - - public abstract void writeTo(JceOutputStream var1); -} diff --git a/mirai-debug/src/test/java/jce/jce/JceUtil.java b/mirai-debug/src/test/java/jce/jce/JceUtil.java deleted file mode 100644 index a41763e75..000000000 --- a/mirai-debug/src/test/java/jce/jce/JceUtil.java +++ /dev/null @@ -1,594 +0,0 @@ -package jce.jce; - -import java.nio.ByteBuffer; -import java.util.Iterator; -import java.util.List; - -public final class JceUtil { - private static final byte[] highDigits; - private static final int iConstant = 37; - private static final int iTotal = 17; - private static final byte[] lowDigits; - - static { - byte[] var1 = new byte[]{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70}; - byte[] var2 = new byte[256]; - byte[] var3 = new byte[256]; - - for (int var0 = 0; var0 < 256; ++var0) { - var2[var0] = var1[var0 >>> 4]; - var3[var0] = var1[var0 & 15]; - } - - highDigits = var2; - lowDigits = var3; - } - - public static int compareTo(byte var0, byte var1) { - if (var0 < var1) { - return -1; - } else { - return var0 > var1 ? 1 : 0; - } - } - - public static int compareTo(char var0, char var1) { - if (var0 < var1) { - return -1; - } else { - return var0 > var1 ? 1 : 0; - } - } - - public static int compareTo(double var0, double var2) { - if (var0 < var2) { - return -1; - } else { - return var0 > var2 ? 1 : 0; - } - } - - public static int compareTo(float var0, float var1) { - if (var0 < var1) { - return -1; - } else { - return var0 > var1 ? 1 : 0; - } - } - - public static int compareTo(int var0, int var1) { - if (var0 < var1) { - return -1; - } else { - return var0 > var1 ? 1 : 0; - } - } - - public static int compareTo(long var0, long var2) { - if (var0 < var2) { - return -1; - } else { - return var0 > var2 ? 1 : 0; - } - } - - public static <T extends Comparable<T>> int compareTo(T var0, T var1) { - return var0.compareTo(var1); - } - - public static <T extends Comparable<T>> int compareTo(List<T> var0, List<T> var1) { - Iterator var3 = var0.iterator(); - Iterator var4 = var1.iterator(); - - while (var3.hasNext() && var4.hasNext()) { - int var2 = ((Comparable) var3.next()).compareTo(var4.next()); - if (var2 != 0) { - return var2; - } - } - - return compareTo(var3.hasNext(), var4.hasNext()); - } - - public static int compareTo(short var0, short var1) { - if (var0 < var1) { - return -1; - } else { - return var0 > var1 ? 1 : 0; - } - } - - public static int compareTo(boolean var0, boolean var1) { - byte var3 = 1; - byte var2; - if (var0) { - var2 = 1; - } else { - var2 = 0; - } - - if (!var1) { - var3 = 0; - } - - return var2 - var3; - } - - public static int compareTo(byte[] var0, byte[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static int compareTo(char[] var0, char[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static int compareTo(double[] var0, double[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static int compareTo(float[] var0, float[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static int compareTo(int[] var0, int[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static int compareTo(long[] var0, long[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static <T extends Comparable<T>> int compareTo(T[] var0, T[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = var0[var2].compareTo(var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static int compareTo(short[] var0, short[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static int compareTo(boolean[] var0, boolean[] var1) { - int var3 = 0; - - for (int var2 = 0; var2 < var0.length && var3 < var1.length; ++var2) { - int var4 = compareTo(var0[var2], var1[var3]); - if (var4 != 0) { - return var4; - } - - ++var3; - } - - return compareTo(var0.length, var1.length); - } - - public static boolean equals(byte var0, byte var1) { - return var0 == var1; - } - - public static boolean equals(char var0, char var1) { - return var0 == var1; - } - - public static boolean equals(double var0, double var2) { - return var0 == var2; - } - - public static boolean equals(float var0, float var1) { - return var0 == var1; - } - - public static boolean equals(int var0, int var1) { - return var0 == var1; - } - - public static boolean equals(long var0, long var2) { - return var0 == var2; - } - - public static boolean equals(Object var0, Object var1) { - return var0.equals(var1); - } - - public static boolean equals(short var0, short var1) { - return var0 == var1; - } - - public static boolean equals(boolean var0, boolean var1) { - return var0 == var1; - } - - public static String getHexdump(ByteBuffer var0) { - int var1 = var0.remaining(); - if (var1 == 0) { - return "empty"; - } else { - StringBuffer var4 = new StringBuffer(var0.remaining() * 3 - 1); - int var2 = var0.position(); - int var3 = var0.get() & 255; - var4.append((char) highDigits[var3]); - var4.append((char) lowDigits[var3]); - --var1; - - while (var1 > 0) { - var4.append(' '); - var3 = var0.get() & 255; - var4.append((char) highDigits[var3]); - var4.append((char) lowDigits[var3]); - --var1; - } - - var0.position(var2); - return var4.toString(); - } - } - - public static String getHexdump(byte[] var0) { - return getHexdump(ByteBuffer.wrap(var0)); - } - - public static byte[] getJceBufArray(ByteBuffer var0) { - byte[] var1 = new byte[var0.position()]; - System.arraycopy(var0.array(), 0, var1, 0, var1.length); - return var1; - } - - public static int hashCode(byte var0) { - return var0 + 629; - } - - public static int hashCode(char var0) { - return var0 + 629; - } - - public static int hashCode(double var0) { - return hashCode(Double.doubleToLongBits(var0)); - } - - public static int hashCode(float var0) { - return Float.floatToIntBits(var0) + 629; - } - - public static int hashCode(int var0) { - return var0 + 629; - } - - public static int hashCode(long var0) { - return (int) (var0 >> 32 ^ var0) + 629; - } - - public static int hashCode(Object var0) { - if (var0 == null) { - return 629; - } else if (var0.getClass().isArray()) { - if (var0 instanceof long[]) { - return hashCode((long[]) ((long[]) var0)); - } else if (var0 instanceof int[]) { - return hashCode((int[]) ((int[]) var0)); - } else if (var0 instanceof short[]) { - return hashCode((short[]) ((short[]) var0)); - } else if (var0 instanceof char[]) { - return hashCode((char[]) ((char[]) var0)); - } else if (var0 instanceof byte[]) { - return hashCode((byte[]) ((byte[]) var0)); - } else if (var0 instanceof double[]) { - return hashCode((double[]) ((double[]) var0)); - } else if (var0 instanceof float[]) { - return hashCode((float[]) ((float[]) var0)); - } else if (var0 instanceof boolean[]) { - return hashCode((boolean[]) ((boolean[]) var0)); - } else { - return var0 instanceof JceStruct[] ? hashCode((JceStruct[]) ((JceStruct[]) var0)) : hashCode((Object) ((Object[]) ((Object[]) var0))); - } - } else { - return var0 instanceof JceStruct ? var0.hashCode() : var0.hashCode() + 629; - } - } - - public static int hashCode(short var0) { - return var0 + 629; - } - - public static int hashCode(boolean var0) { - byte var1; - if (var0) { - var1 = 0; - } else { - var1 = 1; - } - - return var1 + 629; - } - - public static int hashCode(byte[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + var0[var2]; - ++var2; - } - } - - return var3; - } - - public static int hashCode(char[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + var0[var2]; - ++var2; - } - } - - return var3; - } - - public static int hashCode(double[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + (int) (Double.doubleToLongBits(var0[var2]) ^ Double.doubleToLongBits(var0[var2]) >> 32); - ++var2; - } - } - - return var3; - } - - public static int hashCode(float[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + Float.floatToIntBits(var0[var2]); - ++var2; - } - } - - return var3; - } - - public static int hashCode(int[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + var0[var2]; - ++var2; - } - } - - return var3; - } - - public static int hashCode(long[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + (int) (var0[var2] ^ var0[var2] >> 32); - ++var2; - } - } - - return var3; - } - - public static int hashCode(JceStruct[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + var0[var2].hashCode(); - ++var2; - } - } - - return var3; - } - - public static int hashCode(short[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - var1 = var1 * 37 + var0[var2]; - ++var2; - } - } - - return var3; - } - - public static int hashCode(boolean[] var0) { - int var3; - if (var0 == null) { - var3 = 629; - } else { - int var1 = 17; - int var2 = 0; - - while (true) { - var3 = var1; - if (var2 >= var0.length) { - break; - } - - byte var4; - if (var0[var2]) { - var4 = 0; - } else { - var4 = 1; - } - - var1 = var4 + var1 * 37; - ++var2; - } - } - - return var3; - } -} diff --git a/mirai-debug/src/test/java/jce/jce/OnIllegalArgumentException.java b/mirai-debug/src/test/java/jce/jce/OnIllegalArgumentException.java deleted file mode 100644 index af7eb6cee..000000000 --- a/mirai-debug/src/test/java/jce/jce/OnIllegalArgumentException.java +++ /dev/null @@ -1,7 +0,0 @@ -package jce.jce; - -import java.nio.ByteBuffer; - -public interface OnIllegalArgumentException { - void onException(IllegalArgumentException var1, ByteBuffer var2, int var3, int var4); -} diff --git a/mirai-demos/build.gradle b/mirai-demos/build.gradle deleted file mode 100644 index e69de29bb..000000000 diff --git a/mirai-demos/mirai-demo-1/build.gradle b/mirai-demos/mirai-demo-1/build.gradle deleted file mode 100644 index 0afcdc380..000000000 --- a/mirai-demos/mirai-demo-1/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -apply plugin: "kotlin" -apply plugin: "java" - -dependencies { - implementation files("../../mirai-core/build/classes/kotlin/jvm/main") // IDE bug - - implementation files("../../mirai-core-qqandroid/build/classes/kotlin/jvm/main") // IDE bug - implementation project(":mirai-core-qqandroid") - - api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion - api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion -} diff --git a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt b/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt deleted file mode 100644 index 79c921519..000000000 --- a/mirai-demos/mirai-demo-1/src/main/java/demo/subscribe/SubscribeSamples.kt +++ /dev/null @@ -1,254 +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 - */ - -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") - -package demo.subscribe - -import kotlinx.coroutines.CompletableJob -import net.mamoe.mirai.Bot -import net.mamoe.mirai.BotAccount -import net.mamoe.mirai.alsoLogin -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.contact.sendMessage -import net.mamoe.mirai.event.* -import net.mamoe.mirai.message.FriendMessage -import net.mamoe.mirai.message.GroupMessage -import net.mamoe.mirai.message.data.AtAll -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.data.PlainText -import net.mamoe.mirai.message.data.firstOrNull -import net.mamoe.mirai.message.sendAsImageTo -import net.mamoe.mirai.qqandroid.Bot -import net.mamoe.mirai.qqandroid.QQAndroid -import net.mamoe.mirai.utils.FileBasedDeviceInfo -import java.io.File - -private fun readTestAccount(): BotAccount? { - val file = File("testAccount.txt") - if (!file.exists() || !file.canRead()) { - return null - } - - println("Reading account from testAccount.text") - val lines = file.readLines() - return try { - BotAccount(lines[0].toLong(), lines[1]) - } catch (e: IndexOutOfBoundsException) { - null - } -} - -@Suppress("UNUSED_VARIABLE") -suspend fun main() { - val bot = QQAndroid.Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数 - 123456789, - "123456" - ) { - // 覆盖默认的配置 - +FileBasedDeviceInfo // 使用 "device.json" 保存设备信息 - // networkLoggerSupplier = { SilentLogger } // 禁用网络层输出 - }.alsoLogin() - - bot.messageDSL() - directlySubscribe(bot) - - bot.network.awaitDisconnection()//等到直到断开连接 -} - -/** - * 使用 dsl 监听消息事件 - * - * @see subscribeFriendMessages - * @see subscribeMessages - * @see subscribeGroupMessages - * - * @see MessageSubscribersBuilder - */ -fun Bot.messageDSL() { - // 监听这个 bot 的来自所有群和好友的消息 - this.subscribeMessages { - // 当接收到消息 == "你好" 时就回复 "你好!" - "你好" reply "你好!" - - // 当消息 == "查看 subject" 时, 执行 lambda - case("查看 subject") { - if (subject is QQ) { - reply("消息主体为 QQ, 你在发私聊消息") - } else { - reply("消息主体为 Group, 你在群里发消息") - } - - // 在回复的时候, 一般使用 subject 来作为回复对象. - // 因为当群消息时, subject 为这个群. - // 当好友消息时, subject 为这个好友. - // 所有在 MessagePacket(也就是此时的 this 指代的对象) 中实现的扩展方法, 如刚刚的 "reply", 都是以 subject 作为目标 - } - - - // 当消息里面包含这个类型的消息时 - has<Image> { - // this: MessagePacket - // message: MessageChain - // sender: QQ - // it: String (MessageChain.toString) - - - // message[Image].download() // 还未支持 download - if (this is GroupMessage) { - //如果是群消息 - // group: Group - this.group.sendMessage("你在一个群里") - // 等同于 reply("你在一个群里") - } - - reply("图片, ID= ${message[Image]}")//获取第一个 Image 类型的消息 - reply(message) - } - - "hello.*world".toRegex() matchingReply { - "Hello!" - } - - "123" containsReply "你的消息里面包含 123" - - - // 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String - "我的qq" reply { sender.id } - - "at all" reply AtAll // at 全体成员 - - // 如果是这个 QQ 号发送的消息(可以是好友消息也可以是群消息) - sentBy(123456789) { - } - - - // 当消息前缀为 "我是" 时 - startsWith("我是", removePrefix = true) { - // it: 删除了消息前缀 "我是" 后的消息 - // 如一条消息为 "我是张三", 则此时的 it 为 "张三". - - reply("你是$it") - } - - - // listener 管理 - - var repeaterListener: CompletableJob? = null - contains("开启复读") { - repeaterListener?.complete() - bot.subscribeGroupMessages { - repeaterListener = contains("复读") { - reply(message) - } - } - - } - - contains("关闭复读") { - if (repeaterListener?.complete() == null) { - reply("没有开启复读") - } else { - reply("成功关闭复读") - } - } - - - // 自定义的 filter, filter 中 it 为转为 String 的消息. - // 也可以用任何能在处理时使用的变量, 如 subject, sender, message - content({ it.length == 3 }) { - reply("你发送了长度为 3 的消息") - } - - - case("上传好友图片") { - val filename = it.substringAfter("上传好友图片") - File("C:\\Users\\Him18\\Desktop\\$filename").sendAsImageTo(subject) - } - - case("上传群图片") { - val filename = it.substringAfter("上传好友图片") - File("C:\\Users\\Him18\\Desktop\\$filename").sendAsImageTo(subject) - } - } - - subscribeMessages { - case("你好") { - // this: MessagePacket - // message: MessageChain - // sender: QQ - // it: String (来自 MessageChain.toString) - // group: Group (如果是群消息) - reply("你好") - } - } - - subscribeFriendMessages { - contains("A") { - // this: FriendMessage - // message: MessageChain - // sender: QQ - // it: String (来自 MessageChain.toString) - reply("B") - } - } - - subscribeGroupMessages { - // this: FriendMessage - // message: MessageChain - // sender: QQ - // it: String (来自 MessageChain.toString) - // group: Group - } -} - -/** - * 监听单个事件 - */ -@Suppress("UNUSED_VARIABLE") -suspend fun directlySubscribe(bot: Bot) { - // 在当前协程作用域 (CoroutineScope) 下创建一个子 Job, 监听一个事件. - // - // 手动处理消息 - // - // subscribeAlways 函数返回 Listener, Listener 是一个 CompletableJob. - // - // 例如: - // ```kotlin - // runBlocking {// this: CoroutineScope - // subscribeAlways<FriendMessage> { - // } - // } - // ``` - // 则这个 `runBlocking` 永远不会结束, 因为 `subscribeAlways` 在 `runBlocking` 的 `CoroutineScope` 下创建了一个 Job. - // 正确的用法为: - // 在 Bot 的 CoroutineScope 下创建一个监听事件的 Job, 则这个子 Job 会在 Bot 离线后自动完成 (complete). - bot.subscribeAlways<FriendMessage> { - // this: FriendMessageEvent - // event: FriendMessageEvent - - // 获取第一个纯文本消息, 获取不到会抛出 NoSuchElementException - // val firstText = message.first<PlainText>() - - val firstText = message.firstOrNull<PlainText>() - - // 获取第一个图片 - val firstImage = message.firstOrNull<Image>() - - when { - message eq "你好" -> reply("你好!") - - "复读" in message -> sender.sendMessage(message) - - "发群消息" in message -> { - bot.getGroup(580266363).sendMessage(message.toString().substringAfter("发群消息")) - } - } - } -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/build.gradle b/mirai-demos/mirai-demo-android/build.gradle deleted file mode 100644 index 0cadf3aa8..000000000 --- a/mirai-demos/mirai-demo-android/build.gradle +++ /dev/null @@ -1,65 +0,0 @@ -plugins { - id 'com.android.application' - id 'org.jetbrains.kotlin.multiplatform' - id 'kotlin-android-extensions' -} - -android { - compileSdkVersion 29 - defaultConfig { - applicationId "net.mamoe.mirai.demo" - minSdkVersion 21 - targetSdkVersion 29 - versionCode 1 - versionName "1.0" - } - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - packagingOptions { - exclude 'META-INF/main.kotlin_module' - exclude 'META-INF/ktor-http.kotlin_module' - exclude 'META-INF/kotlinx-io.kotlin_module' - exclude 'META-INF/atomicfu.kotlin_module' - exclude 'META-INF/ktor-utils.kotlin_module' - exclude 'META-INF/kotlinx-coroutines-io.kotlin_module' - exclude 'META-INF/kotlinx-coroutines-core.kotlin_module' - exclude 'META-INF/ktor-http-cio.kotlin_module' - exclude 'META-INF/ktor-http-cio.kotlin_module' - exclude 'META-INF/ktor-client-core.kotlin_module' - exclude "META-INF/kotlinx-serialization-runtime.kotlin_module" - exclude 'META-INF/ktor-io.kotlin_module' - } -} - -kotlin { - targets.fromPreset(presets.android, 'android') -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib" - implementation project(':mirai-core-qqandroid') - - implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion - implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion - implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-android', version: "1.3.2" - - //implementation 'com.android.support:appcompat-v7:29.1.1'// https://mvnrepository.com/artifact/androidx.appcompat/appcompat - implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.1.0' - - - testImplementation "org.jetbrains.kotlin:kotlin-test" - testImplementation 'junit:junit:4.12' - - androidTestImplementation 'junit:junit:4.12' - - - def anko_version = "0.10.8" - implementation "org.jetbrains.anko:anko-commons:$anko_version" - - implementation group: 'io.ktor', name: 'ktor-client-android', version: '1.2.5' - implementation("io.ktor:ktor-client-android:1.2.5") -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/proguard-rules.pro b/mirai-demos/mirai-demo-android/proguard-rules.pro deleted file mode 100644 index cf504086a..000000000 --- a/mirai-demos/mirai-demo-android/proguard-rules.pro +++ /dev/null @@ -1,22 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile - diff --git a/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml b/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml deleted file mode 100644 index c0a1c3165..000000000 --- a/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,25 +0,0 @@ -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="net.mamoe.mirai.demo"> - - <uses-permission android:name="android.permission.INTERNET"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> - - <application - android:label="@string/app_name" - android:allowBackup="true" - android:supportsRtl="true" - android:theme="@style/AppTheme"> - - <activity - android:name="net.mamoe.mirai.demo.MainActivity" - android:theme="@style/AppTheme"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - </activity> - - <service android:name=".MiraiService"/> - </application> -</manifest> diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt deleted file mode 100644 index 4495f3dc1..000000000 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt +++ /dev/null @@ -1,20 +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.demo - -import android.graphics.Bitmap - -interface LoginCallback { - - suspend fun onCaptcha(bitmap: Bitmap) - suspend fun onSuccess() - suspend fun onFailed() - suspend fun onMessage(message:String) -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt deleted file mode 100644 index 4c30a9bb2..000000000 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt +++ /dev/null @@ -1,123 +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 - */ - -@file:Suppress("DEPRECATION", "EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.demo - -import android.annotation.SuppressLint -import android.app.ProgressDialog -import android.app.Service -import android.content.ComponentName -import android.content.Intent -import android.content.ServiceConnection -import android.graphics.Bitmap -import android.os.Bundle -import android.os.IBinder -import android.view.View -import android.widget.Toast -import androidx.appcompat.app.AppCompatActivity -import kotlinx.android.synthetic.main.activity_main.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class MainActivity : AppCompatActivity(),LoginCallback { - - - private lateinit var progressDialog : ProgressDialog - - override suspend fun onCaptcha(bitmap: Bitmap) { - withContext(Dispatchers.Main){ - ll_captcha.visibility = View.VISIBLE - iv_captcha.setImageBitmap(bitmap) - needCaptcha = true - if (progressDialog.isShowing){ - progressDialog.dismiss() - } - } - } - - @SuppressLint("SetTextI18n") - override suspend fun onMessage(message:String) { - withContext(Dispatchers.Main){ - msg.text = "${msg.text}\n$message" - } - } - - override suspend fun onSuccess() { - withContext(Dispatchers.Main){ - Toast.makeText(this@MainActivity,"登录成功",Toast.LENGTH_SHORT).show() - if (progressDialog.isShowing){ - progressDialog.dismiss() - } - ll_captcha.visibility = View.GONE - et_pwd.visibility = View.GONE - et_qq.visibility = View.GONE - bt_login.visibility = View.GONE - } - - } - - override suspend fun onFailed() { - withContext(Dispatchers.Main){ - Toast.makeText(this@MainActivity,"登录失败",Toast.LENGTH_SHORT).show() - if (progressDialog.isShowing){ - progressDialog.dismiss() - } - } - } - - var binder: MiraiService.MiraiBinder? = null - - private var needCaptcha = false - - - private val conn = object : ServiceConnection { - - override fun onServiceConnected(name: ComponentName?, service: IBinder?) { - binder = service as MiraiService.MiraiBinder? - } - - override fun onServiceDisconnected(name: ComponentName?) { - binder = null - } - - } - - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - val intent = Intent(this, MiraiService::class.java) - startService(intent) - bindService(intent, conn, Service.BIND_AUTO_CREATE) - progressDialog = ProgressDialog(this) - bt_login.setOnClickListener { - if (!progressDialog.isShowing){ - progressDialog.show() - } - binder?.setCallback(this) - if (!needCaptcha){ - val qq = et_qq.text.toString().toLong() - val pwd = et_pwd.text.toString() - binder?.startLogin(qq, pwd) - }else{ - val captcha = et_captcha.text.toString() - binder?.setCaptcha(captcha) - } - - } - } - - override fun onDestroy() { - super.onDestroy() - unbindService(conn) - } - -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt deleted file mode 100644 index 667cf122a..000000000 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt +++ /dev/null @@ -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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.demo - -import android.app.Service -import android.content.Context -import android.content.Intent -import android.graphics.BitmapFactory -import android.os.Binder -import android.os.IBinder -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.io.core.IoBuffer -import kotlinx.io.core.readBytes -import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.subscribeMessages -import net.mamoe.mirai.qqandroid.QQAndroid -import net.mamoe.mirai.utils.LoginSolver -import java.lang.ref.WeakReference - -class MiraiService : Service() { - - private lateinit var mCaptchaDeferred: CompletableDeferred<String> - - private lateinit var mBot: Bot - - private var mCaptcha = "" - set(value) { - field = value - mCaptchaDeferred.complete(value) - } - - private var mBinder: MiraiBinder? = null - - private var mCallback: WeakReference<LoginCallback>? = null - - override fun onCreate() { - super.onCreate() - mBinder = MiraiBinder() - - } - - private fun login(context: Context, qq: Long, pwd: String) { - GlobalScope.launch { - mBot = QQAndroid.Bot(context, qq, pwd) { - loginSolver = object : LoginSolver() { - override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? { - val bytes = data.readBytes() - val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size) - mCaptchaDeferred = CompletableDeferred() - mCallback?.get()?.onCaptcha(bitmap) - return mCaptchaDeferred.await() - } - - override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { - TODO("not implemented") - } - - override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { - TODO("not implemented") - } - - } - }.apply { - try { - login() - mCallback?.get()?.onSuccess() - } catch (e: Exception) { - mCallback?.get()?.onFailed() - } - } - - - mBot.subscribeMessages { - always { - mCallback?.get()?.onMessage("收到来自${sender.id}的消息") - } - - // 当接收到消息 == "你好" 时就回复 "你好!" - "你好" reply "你好!" - } - } - - } - - - override fun onBind(intent: Intent?): IBinder? { - return mBinder - } - - - inner class MiraiBinder : Binder() { - - fun startLogin(qq: Long, pwd: String) { - login(applicationContext, qq, pwd) - } - - fun setCaptcha(captcha: String) { - mCaptcha = captcha - } - - fun setCallback(callback: LoginCallback) { - mCallback = WeakReference(callback) - } - } - - -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml b/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml deleted file mode 100644 index ce8b8004b..000000000 --- a/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,62 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:scrollbars="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="vertical"> - - <EditText - android:layout_width="match_parent" - android:layout_height="46dp" - android:inputType="number" - android:layout_marginTop="10dp" - android:ems="15" - android:hint="请输入QQ号" - android:id="@+id/et_qq" /> - - <EditText - android:layout_width="match_parent" - android:layout_height="46dp" - android:inputType="textPassword" - android:hint="请输入密码" - android:layout_marginTop="10dp" - android:id="@+id/et_pwd" /> - - <LinearLayout - android:id="@+id/ll_captcha" - android:layout_marginTop="5dp" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <EditText - android:id="@+id/et_captcha" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="46dp" - android:hint="输入验证码"/> - - <ImageView - android:id="@+id/iv_captcha" - android:layout_weight="1" - android:layout_width="0dp" - android:layout_height="match_parent" - android:layout_marginLeft="5dp" - android:scaleType="fitXY"/> - </LinearLayout> - - <Button - android:text="登录" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginTop="5dp" - android:id="@+id/bt_login" - android:background="#3F51B5" - android:textColor="#FFFFFF"/> - - <TextView - android:id="@+id/msg" - android:layout_width="match_parent" - android:layout_height="wrap_content"/> - -</LinearLayout> \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/res/values/colors.xml b/mirai-demos/mirai-demo-android/src/main/res/values/colors.xml deleted file mode 100644 index 3ab3e9cbc..000000000 --- a/mirai-demos/mirai-demo-android/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <color name="colorPrimary">#3F51B5</color> - <color name="colorPrimaryDark">#303F9F</color> - <color name="colorAccent">#FF4081</color> -</resources> diff --git a/mirai-demos/mirai-demo-android/src/main/res/values/strings.xml b/mirai-demos/mirai-demo-android/src/main/res/values/strings.xml deleted file mode 100644 index 3b2ef2eb7..000000000 --- a/mirai-demos/mirai-demo-android/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<resources> - <string name="app_name">Mirai</string> -</resources> \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/res/values/styles.xml b/mirai-demos/mirai-demo-android/src/main/res/values/styles.xml deleted file mode 100644 index 5885930df..000000000 --- a/mirai-demos/mirai-demo-android/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ -<resources> - - <!-- Base application theme. --> - <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> - <!-- Customize your theme here. --> - <item name="colorPrimary">@color/colorPrimary</item> - <item name="colorPrimaryDark">@color/colorPrimaryDark</item> - <item name="colorAccent">@color/colorAccent</item> - </style> - -</resources> diff --git a/mirai-demos/mirai-demo-gentleman/build.gradle b/mirai-demos/mirai-demo-gentleman/build.gradle deleted file mode 100644 index 4c8c12fd6..000000000 --- a/mirai-demos/mirai-demo-gentleman/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -apply plugin: "kotlin" -apply plugin: "java" -apply plugin: "application" -apply plugin: "kotlinx-serialization" - -dependencies { - runtimeOnly files("../../mirai-core/build/classes/kotlin/jvm/main") // IDE bug - runtimeOnly files("../../mirai-core-qqandroid/build/classes/kotlin/jvm/main") // IDE bug - api project(":mirai-core") - api project(":mirai-core-qqandroid") - - api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion") - implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion - implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion - - implementation("org.jetbrains.kotlinx:kotlinx-io:$kotlinXIoVersion") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion") - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-io:$coroutinesIoVersion") - implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.62' - api 'org.jsoup:jsoup:1.12.1' -} - -run{ - standardInput = System.in - mainClassName = "demo.gentleman.MainKt" -} -compileKotlin { - kotlinOptions { - freeCompilerArgs = ["-XXLanguage:+InlineClasses"] - } -} diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt deleted file mode 100644 index f152ebb88..000000000 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/GentleImage.kt +++ /dev/null @@ -1,72 +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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE") - -package demo.gentleman - -import kotlinx.coroutines.* -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.uploadAsImage -import org.jsoup.Jsoup - -class GentleImage(val contact: Contact, val keyword: String) { - - val image: Deferred<Image> by lazy { getImage(0) } - - val seImage: Deferred<Image> by lazy { getImage(1) } - - fun getImage(r18: Int): Deferred<Image> { - return GlobalScope.async { - withTimeoutOrNull(20 * 1000) { - withContext(Dispatchers.IO) { - - @Serializable - data class Setu( - val url: String, - val pid: String - ) - - @Serializable - data class Result( - val data: List<Setu> - ) - - val result = - Json.nonstrict.parse( - Result.serializer(), - Jsoup.connect("https://api.lolicon.app/setu/?r18=$r18" + if (keyword.isNotBlank()) "&keyword=$keyword&num=10" else "") - .ignoreContentType(true) - .userAgent(UserAgent.randomUserAgent) - // .proxy("127.0.0.1", 1088) - .timeout(10_0000) - .get().body().text() - ) - - val setu = result.data.random() - Jsoup - .connect(setu.url) - .followRedirects(true) - .timeout(180_000) - .ignoreContentType(true) - .userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27") - .referrer("https://www.pixiv.net/member_illust.php?mode=medium&illust_id=${setu.pid}") - // .proxy("127.0.0.1", 1088) - .ignoreHttpErrors(true) - .maxBodySize(10000000) - .execute().also { check(it.statusCode() == 200) { "Failed to download image" } } - } - }?.bodyStream()?.uploadAsImage(contact) ?: error("Unable to download image") - } - } -} - diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Gentleman.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Gentleman.kt deleted file mode 100644 index 77adc9e1c..000000000 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Gentleman.kt +++ /dev/null @@ -1,48 +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 demo.gentleman - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.launch -import net.mamoe.mirai.contact.Contact - - -/** - * 最少缓存的图片数量 - */ -private const val IMAGE_BUFFER_CAPACITY: Int = 5 - -/** - * 为不同的联系人提供图片 - */ -@ExperimentalUnsignedTypes -@ExperimentalCoroutinesApi -object Gentlemen : MutableMap<Long, Gentleman> by mutableMapOf() { - fun provide(key: Contact, keyword: String = ""): Gentleman = this.getOrPut(key.id) { Gentleman(key, keyword) } -} - -/** - * 工作是缓存图片 - */ -@ExperimentalCoroutinesApi -class Gentleman(private val contact: Contact, private val keyword: String) : Channel<GentleImage> by Channel(IMAGE_BUFFER_CAPACITY) { - init { - - GlobalScope.launch { - while (!isClosedForSend) { - send(GentleImage(contact, keyword).apply { - seImage// start downloading - }) - } - } - } -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt deleted file mode 100644 index bd15503f9..000000000 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/Main.kt +++ /dev/null @@ -1,183 +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 - */ - -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") - -package demo.gentleman - -import kotlinx.coroutines.Dispatchers.IO -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import net.mamoe.mirai.Bot -import net.mamoe.mirai.alsoLogin -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.contact.MemberPermission -import net.mamoe.mirai.event.Event -import net.mamoe.mirai.event.events.BotEvent -import net.mamoe.mirai.event.subscribeAlways -import net.mamoe.mirai.event.subscribeGroupMessages -import net.mamoe.mirai.event.subscribeMessages -import net.mamoe.mirai.message.FriendMessage -import net.mamoe.mirai.message.GroupMessage -import net.mamoe.mirai.message.data.At -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.data.buildXMLMessage -import net.mamoe.mirai.message.data.getValue -import net.mamoe.mirai.message.sendAsImageTo -import net.mamoe.mirai.utils.ContextImpl -import java.io.File -import java.util.* -import javax.swing.filechooser.FileSystemView -import kotlin.random.Random - -@Suppress("UNUSED_VARIABLE") -suspend fun main() { - val bot = Bot( - ContextImpl(), - 913366033, - "a18260132383" - ) { - // override config here. - }.alsoLogin() - - // 任何可以监听的对象都继承 Event, 因此这个订阅会订阅全部的事件. - GlobalScope.subscribeAlways<Event> { - //bot.logger.verbose("收到了一个事件: $this") - } - - // 全局范围订阅事件, 不受 bot 实例影响 - GlobalScope.subscribeAlways<BotEvent> { - - } - - // 订阅来自这个 bot 的群消息事件 - bot.subscribeGroupMessages { - startsWith("mute") { - val at: At by message - at.member().mute(30) - } - - startsWith("unmute") { - val at: At by message - at.member().unmute() - } - } - - // 订阅来自这个 bot 的消息事件, 可以是群消息也可以是好友消息 - bot.subscribeMessages { - always { - } - - case("at me") { At(sender as Member).reply() } - // 等同于 "at me" reply { At(sender) } - - "你好" reply "你好!" - - "grouplist" reply { - - //"https://ssl.ptlogin2.qq.com/jump?pt_clientver=5509&pt_src=1&keyindex=9&clientuin=" + bot.qqAccount + "&clientkey=" + com.tick_tock.pctim.utils.Util.byte2HexString( - // user.txprotocol.serviceTicketHttp - //).replace(" ", "").toString() + "&u1=http%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D168209441" - } - - "xml" reply { - - val template = - """ - <?xml version='1.0' encoding='UTF-8' standalone='yes' ?> - <msg templateID='1' serviceID='1' action='plugin' actionData='ACTION_LINK' brief='BRIEF' flag='3' url=''> - <item bg='0' layout='4'> - <picture cover='TITLE_PICTURE_LINK'/> - <title size='30' color='#fc7299'>TITLE</title> - </item> - <item> - <summary color='#fc7299'>CONTENT</summary> - <picture cover='CONTENT_PICTURE_LINK'/> - </item> - <source name='ExHentai' icon='ExHentai'/> - </msg> - """.trimIndent() - - buildXMLMessage { - item { - picture("http://img.mamoe.net/2019/12/03/be35ccb489ecb.jpg") - title("This is title") - } - - item { - summary("This is a summary colored #66CCFF", color = "#66CCFF") - picture("http://img.mamoe.net/2019/12/03/74c8614c4a161.jpg") - } - - source("Mirai", "http://img.mamoe.net/2019/12/03/02eea0f6e826a.png") - }.reply() - } - - (contains("1") and has<Image>()){ - reply("Your message has a string \"1\" and an image contained") - } - - has<Image> { - if (this is FriendMessage || (this is GroupMessage && this.permission == MemberPermission.ADMINISTRATOR)) withContext(IO) { - val image: Image by message - // 等同于 val image = message[Image] - - try { - image.downloadTo(newTestTempFile(suffix = ".png").also { reply("Temp file: ${it.absolutePath}") }) - reply(image.imageId + " downloaded") - } catch (e: Exception) { - e.printStackTrace() - reply(e.message ?: e::class.java.simpleName) - } - } - - } - - startsWith("上传图片", removePrefix = true) handler@{ - val file = File(FileSystemView.getFileSystemView().homeDirectory, it) - if (!file.exists()) { - reply("图片不存在") - return@handler - } - - reply("sent") - file.sendAsImageTo(subject) - } - - startsWith("色图", removePrefix = true) { - repeat(it.toIntOrNull() ?: 1) { - GlobalScope.launch { - delay(Random.Default.nextLong(100, 1000)) - Gentlemen.provide(subject).receive().image.await().send() - } - } - } - - startsWith("不够色", removePrefix = true) { - repeat(it.toIntOrNull() ?: 1) { - GlobalScope.launch { - delay(Random.Default.nextLong(100, 1000)) - Gentlemen.provide(subject).receive().seImage.await().send() - } - } - } - - startsWith("添加好友", removePrefix = true) { - reply(bot.addFriend(it.toLong()).toString()) - } - - } - - bot.network.awaitDisconnection()//等到直到断开连接 -} - -private fun newTestTempFile(filename: String = "${UUID.randomUUID()}", suffix: String = ".tmp"): File = - File(System.getProperty("user.dir"), filename + suffix).also { it.createNewFile(); it.deleteOnExit() } \ No newline at end of file diff --git a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/UserAgent.kt b/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/UserAgent.kt deleted file mode 100644 index fa2fbea62..000000000 --- a/mirai-demos/mirai-demo-gentleman/src/main/kotlin/demo/gentleman/UserAgent.kt +++ /dev/null @@ -1,206 +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 demo.gentleman - -import demo.gentleman.UserAgent.randomUserAgent -import kotlin.random.Random -import kotlin.random.nextInt - -/** - * @see randomUserAgent - */ -object UserAgent : List<String> by listOf( - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.517 Safari/537.36", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.367", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:24.0) Gecko/20100101 Firefox/24.0", - "Mozilla/5.0 (Windows NT 6.4; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.367", - "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.93 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20100101 Firefox/21.07", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1500.55 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:22.0) Gecko/20130328 Firefox/22.07", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.367", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.14 (KHTML, like Gecko) Chrome/24.0.1292.0 Safari/537.147", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2309.372 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.3; rv:36.0) Gecko/20100101 Firefox/36.07", - "Mozilla/5.0 (Windows NT 6.2; WOW64; rv:21.0) Gecko/20130514 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 4.0; InfoPath.3; MS-RTC LM 8; .NET4.0C; .NET4.0E)", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20120101 Firefox/29.0", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2225.0 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.62 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.17 Safari/537.36", - "Mozilla/5.0 (Windows NT 4.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2049.0 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.367", - "Mozilla/5.0 (compatible; MSIE 7.0; Windows NT 5.0; Trident/4.0; FBSMTWB; .NET CLR 2.0.34861; .NET CLR 3.0.3746.3218; .NET CLR 3.5.33652; msn OptimizedIE8;ENUS)", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.367", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2226.0 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36", - "Mozilla/5.0 (X11; NetBSD) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.1; SV1; .NET CLR 2.8.52393; WOW64; en-US)7", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.367", - "Mozilla/5.0 (Windows NT 6.0; WOW64; rv:24.0) Gecko/20100101 Firefox/24.0", - "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.90 Safari/537.367", - "Opera/9.80 (Windows NT 6.1 x64; U; en) Presto/2.7.62 Version/11.00", - "Opera/9.80 (Windows NT 5.2; U; ru) Presto/2.7.62 Version/11.017", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-us) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52", - "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.157", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ja-JP) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; InfoPath.3; .NET4.0C; .NET4.0E; .NET CLR 3.5.30729; .NET CLR 3.0.30729; MS-RTC LM 8)7", - "Mozilla/5.0 (X11; OpenBSD amd64; rv:28.0) Gecko/20100101 Firefox/28.0", - "Mozilla/5.0 (X11; CrOS i686 4319.74.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.3319.102 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; rv:22.0) Gecko/20130405 Firefox/22.07", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1623.0 Safari/537.367", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130331 Firefox/21.07", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:21.0.0) Gecko/20121011 Firefox/21.0.0", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:27.0) Gecko/20121011 Firefox/27.07", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.2117.157 Safari/537.36", - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.137 Safari/4E423F", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.7.39 Version/11.007", - "Mozilla/5.0 (Windows NT 5.1; rv:31.0) Gecko/20100101 Firefox/31.07", - "Mozilla/5.0 (X11; OpenBSD i386) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.60 Safari/537.17", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/4.0; InfoPath.2; SV1; .NET CLR 2.0.50727; WOW64)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; Media Center PC 6.0; InfoPath.3; MS-RTC LM 8; Zune 4.7", - "Mozilla/5.0 (X11; CrOS i686 3912.101.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.367", - "Mozilla/5.0 (Windows x86; rv:19.0) Gecko/20100101 Firefox/19.07", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.367", - "Mozilla/5.0 (Microsoft Windows NT 6.2.9200.0); rv:22.0) Gecko/20130405 Firefox/22.07", - "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1866.237 Safari/537.36", - "Opera/9.80 (Windows NT 5.1; U; zh-tw) Presto/2.8.131 Version/11.10", - "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.93 Safari/537.36", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130331 Firefox/21.0", - "Opera/9.80 (X11; Linux i686; U; ja) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/4.0; GTB7.4; InfoPath.3; SV1; .NET CLR 3.1.76908; WOW64; en-US)7", - "Opera/9.80 (Windows NT 5.1; U; zh-sg) Presto/2.9.181 Version/12.00", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130328 Firefox/21.07", - "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0.6", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; de-de) AppleWebKit/534.15+ (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4", - "Mozilla/5.0 (X11; Linux i686; rv:21.0) Gecko/20100101 Firefox/21.0", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.6.37 Version/11.007", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:25.0) Gecko/20100101 Firefox/25.0", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; zh-cn) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 3.0.04506.30)", - "Mozilla/5.0 (Windows NT 5.0; rv:21.0) Gecko/20100101 Firefox/21.07", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.07", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/13.0.782.215)7", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; Media Center PC 4.0; SLCC1; .NET CLR 3.0.04320)", - "Mozilla/5.0 (Windows NT 6.1; rv:21.0) Gecko/20130401 Firefox/21.07", - "Mozilla/5.0 (compatible; MSIE 10.6; Windows NT 6.1; Trident/5.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727) 3gpp-gba UNTRUSTED/1.07", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:31.0) Gecko/20130401 Firefox/31.0", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:23.0) Gecko/20131011 Firefox/23.07", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; es-es) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130330 Firefox/21.0", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.13) Gecko/20101213 Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_6_8) AppleWebKit/537.13+ (KHTML, like Gecko) Version/5.1.7 Safari/534.57.27", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20130401 Firefox/21.0", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; en-gb) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.07", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)7", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)7", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.75.14 (KHTML, like Gecko) Version/7.0.3 Safari/7046A194A7", - "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/31.0", - "Opera/9.80 (X11; Linux x86_64; U; Ubuntu/10.10 (maverick); pl) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:21.0) Gecko/20100101 Firefox/21.0", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; chromeframe/12.0.742.112)", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1", - "Mozilla/5.0 (Windows NT 6.2; rv:21.0) Gecko/20130326 Firefox/21.07", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; de-DE) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Opera/9.80 (Windows NT 6.1; U; cs) Presto/2.7.62 Version/11.017", - "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/23.07", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; fr-FR) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20130401 Firefox/21.07", - "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:23.0) Gecko/20130406 Firefox/23.0", - "Mozilla/4.0 (Compatible; MSIE 8.0; Windows NT 5.2; Trident/6.0)", - "Opera/9.80 (Windows NT 6.1; U; pl) Presto/2.7.62 Version/11.007", - "Mozilla/5.0 (X11; Linux x86_64; rv:28.0) Gecko/20100101 Firefox/28.0", - "Mozilla/5.0 (Windows NT 5.1; rv:21.0) Gecko/20100101 Firefox/21.07", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.07", - "Opera/9.80 (Windows NT 6.0; U; pl) Presto/2.7.62 Version/11.017", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/9.80 (Windows NT 6.1; U; fi) Presto/2.7.62 Version/11.007", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; InfoPath.1; SV1; .NET CLR 3.8.36217; WOW64; en-US)", - "Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25", - "Mozilla/5.0 (iPad; CPU OS 5_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko ) Version/5.1 Mobile/9B176 Safari/7534.48.3", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; ko-kr) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; tr-TR) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/9.80 (Windows NT 6.1; U; zh-tw) Presto/2.7.62 Version/11.017", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 7.1; Trident/5.0)", - "Opera/9.80 (Windows NT 6.1; U; sv) Presto/2.7.62 Version/11.017", - "Mozilla/5.0 (Windows NT 6.2; Win64; x64;) Gecko/20100101 Firefox/20.0", - "Opera/9.80 (Windows NT 6.1; U; zh-cn) Presto/2.7.62 Version/11.017", - "Mozilla/5.0 (Windows NT 6.1; rv:27.3) Gecko/20130101 Firefox/27.37", - "Opera/9.80 (X11; Linux x86_64; U; pl) Presto/2.7.62 Version/11.007", - "Opera/9.80 (Windows NT 6.1; U; ko) Presto/2.7.62 Version/11.007", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; de-de) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/12.80 (Windows NT 5.1; U; en) Presto/2.10.289 Version/12.027", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; yie8)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; fr-fr) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/9.80 (Windows NT 6.1; WOW64; U; pt) Presto/2.10.229 Version/11.627", - "Opera/9.80 (X11; Linux i686; Ubuntu/14.10) Presto/2.12.388 Version/12.16", - "Mozilla/5.0 (Android 2.2; Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Mozilla/5.0 (Windows NT 6.2; rv:22.0) Gecko/20130405 Firefox/22.07", - "Opera/9.80 (X11; Linux i686; U; fr) Presto/2.7.62 Version/11.01", - "Mozilla/5.0 (compatible, MSIE 11, Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko7", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 87", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; ko-KR) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; de) Presto/2.9.168 Version/11.52", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; de-DE) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; it-it) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; cs-CZ) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; Media Center PC 6.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET4.0C)", - "Opera/9.80 (Windows NT 6.0) Presto/2.12.388 Version/12.14", - "Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; .NET CLR 1.1.4322; .NET4.0C; Tablet PC 2.0)", - "Opera/9.80 (X11; Linux i686; U; it) Presto/2.7.62 Version/11.00", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; GTB7.4; InfoPath.2; SV1; .NET CLR 3.3.69573; WOW64; en-US)7", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; ja-JP) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Opera/9.80 (X11; Linux x86_64; U; fr) Presto/2.9.168 Version/11.50", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; de) Opera 11.51", - "Opera/9.80 (Windows NT 5.1; U; cs) Presto/2.7.62 Version/11.017", - "Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.017", - "Opera/9.80 (Windows NT 6.1; U; es-ES) Presto/2.9.181 Version/12.00", - "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.8.99 Version/11.107", - "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; Media Center PC 6.0; InfoPath.2; MS-RTC LM 8)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; en-us) AppleWebKit/534.16+ (KHTML, like Gecko) Version/5.0.3 Safari/533.19.4", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Zune 3.0)", - "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727)", - "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)7", - "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/12.0(Windows NT 5.1;U;en)Presto/22.9.168 Version/12.00", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0) Opera 12.147", - "Mozilla/5.0 (Windows; U; Windows NT 5.1; it-IT) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.3 Safari/533.19.47", - "Opera/9.80 (Windows NT 5.1; U; en) Presto/2.9.168 Version/11.51", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_6; sv-se) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)", - "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Opera/12.0(Windows NT 5.2;U;en)Presto/22.9.168 Version/12.00", - "Opera/9.80 (X11; Linux x86_64; U; bg) Presto/2.8.131 Version/11.10", - "Mozilla/5.0 (Windows NT 5.1; U; en; rv:1.8.1) Gecko/20061208 Firefox/5.0 Opera 11.11", - "Opera/9.80 (Windows NT 6.1; U; en-GB) Presto/2.7.62 Version/11.007", - "Mozilla/5.0 (Windows; U; Windows NT 6.0; ja-JP) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27", - "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; de) Opera 11.01", - "Mozilla/5.0 (Windows NT 6.0; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 12.14", - "Opera/9.80 (Windows NT 5.1; U;) Presto/2.7.62 Version/11.017" -) { - val randomUserAgent: String = this[Random.Default.nextInt(0 until this.size)] -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-gentleman/src/main/resources/tag.ini b/mirai-demos/mirai-demo-gentleman/src/main/resources/tag.ini deleted file mode 100644 index c827b1240..000000000 --- a/mirai-demos/mirai-demo-gentleman/src/main/resources/tag.ini +++ /dev/null @@ -1,569 +0,0 @@ -[female] -age progression = \u5e74\u9f84\u589e\u957f -age regression = \u8fd4\u8001\u8fd8\u7ae5 -infantilism = \u5e7c\u7a1a\u578b -lolicon = \u841d\u8389 -low lolicon = \u672a\u901a\u8fc7\u841d\u8389 -milf = \u719f\u5973 -old lady = \u8001\u5973\u4eba -toddlercon = \u5e7c\u5973 -amputee = \u622a\u80a2 -body modification = \u8eab\u4f53\u6539\u9020 -conjoined = \u8fde\u4f53 -doll joints = \u5173\u8282\u5a03\u5a03 -gijinka = \u62df\u4eba\u5316 -inflation = \u8179\u90e8\u81a8\u80c0 -invisible = \u900f\u660e -multiple arms = \u591a\u81c2 -multiple breasts = \u591a\u5bf9\u4e73\u623f -muscle = \u808c\u8089 -muscle growth = \u808c\u8089\u6210\u957f -stretching = \u62c9\u4f38 -tailjob = \u5c3e\u4ea4 -wingjob = \u7ffc\u4ea4 -wings = \u7fc5\u8180 -absorption = \u5438\u6536 -petrification = \u77f3\u5316 -transformation = \u53d8\u8eab -alien girl = \u5916\u661f\u5973\ud83d\udc7d -angel = \u5929\u4f7f -bear girl = \u72d7\u718a\u5a18 -bee girl = \u8702\u5973 -bunny girl = \u5154\u5973\u90ce -catgirl = \u732b\u5973 -centaur = \u534a\u4eba\u9a6c -cowgirl = \u725b\u5973\u5b69 -deer girl = \u9e7f\u5973\u5b69 -demon girl = \u6076\u9b54\u5973\u5b69 -dog girl = \u72d7\u5973\u5b69 -draenei = \u5fb7\u83b1\u5c3c -fairy = \u4ed9\u5973\ud83e\uddda\u2640\ufe0f -frog girl = \u9752\u86d9\u5973\u5b69 -fox girl = \u72d0\u5973 -furry = \u6bdb\u8338\u8338 -giraffe girl = \u957f\u9888\u9e7f\u5a18 -ghost = \u5e7d\u7075\ud83d\udc7b -goblin = \u5730\u7cbe -harpy = \u9e1f\u4eba -horse girl = \u9a6c\u5973\u5b69 -human on furry = \u4eba\u6bdb -insect girl = \u6606\u866b\u5973\u5b69 -kappa = \u6cb3\u7ae5 -lizard girl = \u8725\u8734\u5973\u5b69 -mermaid = \u7f8e\u4eba\u9c7c\ud83e\udddc\u2640\ufe0f -monkey girl = \u7334\u5973\u5b69 -monster girl = \u602a\u7269\u5973\u5b69 -mouse girl = \u9f20\u5973\u5b69 -necrophilia = \u5978\u5c38 -oni = \u9b3c -orc = \u517d\u4eba -panda girl = \u718a\u732b\u5a18 -pig girl = \u732a\u5973 -plant girl = \u690d\u7269\u5973\u5b69 -raccoon girl = \u6d63\u718a\u5973\u5b69 -robot = \u673a\u5668\u4eba\ud83e\udd16 -shark girl = \u9ca8\u5973\u5b69 -sheep girl = \u7f8a\u5973\u5b69 -slime = \u53f2\u83b1\u59c6 -slime girl = \u53f2\u83b1\u59c6\u5973\u5b69 -snake girl = \u86c7\u5973 -spider girl = \u8718\u86db\u5a18 -squid girl = \u4e4c\u8d3c\u5a18 -squirrel girl = \u677e\u9f20\u5a18 -tentacles = \u89e6\u624b -vampire = \u5438\u8840\u9b3c\ud83e\udddb\u2640\ufe0f -wolf girl = \u72fc\u5973\u5b69 -zombie = \u50f5\u5c38\ud83e\udddf\u2640\ufe0f -bestiality = \u517d\u4ea4 -animal on animal = \u517d\u517d -animal on furry = \u517d\u6bdb -bear = \u718a\ud83d\udc3b -camel = \u9a86\u9a7c\ud83d\udc2a -cat = \u732b\ud83d\udc08 -cow = \u725b\ud83d\udc04 -crab = \u8783\u87f9\ud83e\udd80 -dinosaur = \u6050\u9f99\ud83e\udd95 -dog = \u72d7\ud83d\udc29 -dolphin = \u6d77\u8c5a\ud83d\udc2c -donkey = \u9a74 -dragon = \u9f99\ud83d\udc09 -eel = \u9cd7\u9c7c -elephant = \u8c61\ud83d\udc18 -fish = \u9c7c\ud83d\udc1f -fox = \u72d0\u72f8\ud83e\udd8a -frog = \u9752\u86d9\ud83d\udc38 -goat = \u5c71\u7f8a\ud83d\udc10 -gorilla = \u7329\u7329\ud83e\udd8d -horse = \u9a6c\ud83d\udc0e -insect = \u6606\u866b\ud83d\udc1c -kangaroo = \u888b\u9f20 -lioness = \u72ee\ud83e\udd81 -low bestiality = \u672a\u901a\u8fc7\u517d\u4ea4 -maggot = \u86c6\ud83d\udc1b -monkey = \u7334\ud83d\udc12 -mouse = \u9f20\ud83d\udc01 -octopus = \u7ae0\u9c7c\ud83e\udd91 -ostrich = \u9e35\u9e1f -panther = \u8c79\ud83d\udc06 -pig = \u732a\ud83d\udc16 -rabbit = \u5154\ud83d\udc07 -reptile = \u722c\u866b -rhinoceros = \u7280\u725b\ud83e\udd8f -sheep = \u7ef5\u7f8a\ud83d\udc11 -shark = \u9ca8\ud83e\udd88 -slug = \u86de\u8753 -snake = \u86c7\ud83d\udc0d -spider = \u8718\u86db\ud83d\udd77 -tiger = \u864e\ud83d\udc05 -turtle = \u9f9f\ud83d\udc22 -unicorn = \u72ec\u89d2\u517d\ud83e\udd84 -whale = \u9cb8\ud83d\udc0b -wolf = \u72fc\ud83d\udc3a -worm = \u8815\u866b -zebra = \u6591\u9a6c\ud83e\udd93 -giantess = \u5973\u5de8\u4eba -growth = \u5de8\u5927\u5316 -midget = \u4f8f\u5112 -minigirl = \u8ff7\u4f60\u5973\u5b69 -shrinking = \u7f29\u5c0f -tall girl = \u9ad8\u4e2a\u5973 -albino = \u767d\u5316 -body writing = \u8eab\u4f53\u5199\u4f5c -body painting = \u8eab\u4f53\u7ed8\u753b -dark skin = \u80a4\u8272\u9edd\u9ed1 -freckles = \u96c0\u6591 -gyaru = \u8fa3\u59b9 -large tattoo = \u5168\u8eab\u7eb9\u8eab -scar = \u7622\u75d5 -skinsuit = \u76ae\u7269 -tanlines = \u6652\u75d5 -anorexic = \u7626\u9aa8\u5d99\u5ccb -bbw = \u80d6\u5973\u4eba -ssbbw = \u8d85\u7ea7\u80d6\u5973\u4eba -weight gain = \u4f53\u91cd\u589e\u52a0 -ahegao = \u963f\u9ed1\u989c -beauty mark = \u7f8e\u4eba\u75e3 -brain fuck = \u8111\u4ea4\ud83e\udde0 -cockslapping = \u5c4c\u63b4 -crown = \u738b\u51a0\ud83d\udc51 -ear fuck = \u8033\u4ea4\ud83d\udc42 -elf = \u7cbe\u7075\ud83e\udddd\u2640\ufe0f -facesitting = \u5750\u8138 -gasmask = \u9632\u6bd2\u9762\u5177 -horns = \u89d2 -makeup = \u5316\u5986 -kemonomimi = \u517d\u8033 -masked face = \u5047\u9762 -thick eyebrows = \u6d53\u7709 -arfo = \u7206\u70b8\u5934 -bald = \u79c3\u9876 -hair buns = \u4e38\u5b50\u5934 -hairjob = \u53d1\u4e1d\u4ea4 -pixie cut = \u7cbe\u7075\u5934 -ponytail = \u9a6c\u5c3e\u8fab -prehensile hair = \u6293\u63e1\u53d1 -shaved head = \u5149\u5934 -twintails = \u53cc\u9a6c\u5c3e -body swap = \u6362\u8eab -chloroform = \u8ff7\u836f -corruption = \u5815\u843d -drugs = \u836f\u7269 -drunk = \u9189\u9152 -emotionless sex = \u6027\u51b7\u6de1 -mind break = \u6d17\u8111 -mind control = \u7cbe\u795e\u63a7\u5236 -moral degeneration = \u9053\u5fb7\u9000\u5316 -parasite = \u5bc4\u751f -possession = \u51ed\u4f9d -shared senses = \u611f\u5b98\u5171\u4eab -sleeping = \u7761\u89c9 -blindfold = \u906e\u773c\u5e03 -closed eyes = \u95ed\u773c -cum in eye = \u773c\u5c04 -dark sclera = \u6df1\u8272\u5de9\u819c -eye penetration = \u63d2\u5165\u773c\u775b -eyemask = \u773c\u90e8\u9762\u5177 -eyepatch = \u773c\u7f69 -glasses = \u773c\u955c\ud83d\udc53 -heterochromia = \u5f02\u8272\u77b3 -monoeye = \u72ec\u773c -sunglasses = \u592a\u9633\u955c\ud83d\udd76 -unusual pupils = \u5f02\u77b3 -nose fuck = \u9f3b\u4ea4 -nose hook = \u9f3b\u540a\u94a9 -smell = \u6c14\u5473 -big lips = \u5927\u5634\u5507\ud83d\udc8b -blowjob = \u53e3\u4ea4 -blowjob face = \u53e3\u4ea4\u989c -braces = \u7259\u5957 -burping = \u6253\u55dd -coprophagia = \u98df\u7caa -deepthroat = \u6df1\u5589 -double blowjob = \u53cc\u91cd\u53e3\u4ea4 -foot licking = \u8214\u8db3 -gag = \u53e3\u585e -gokkun = \u996e\u7cbe -kissing = \u63a5\u543b\ud83d\udc8f -long tongue = \u957f\u820c\ud83d\udc45 -piss drinking = \u996e\u5c3f -rimjob = \u8214\u809b -saliva = \u553e\u6db2 -smoking = \u5438\u70df\ud83d\udeac -tooth brushing = \u5237\u7259 -unusual teeth = \u5f02\u9f7f -vomit = \u5455\u5410\ud83e\udd2e -vore = \u541e\u98df -asphyxiation = \u7a92\u606f -collar = \u9879\u5708 -leash = \u72d7\u94fe -armpit licking = \u814b\u4e0b\u8214 -armpit sex = \u814b\u4ea4 -hairy armpits = \u814b\u6bdb -fingering = \u6307\u6cd5 -fisting = \u62f3\u4ea4\ud83d\udcaa -gloves = \u624b\u5957 -handjob = \u6253\u624b\u67aa -big areolae = \u5927\u4e73\u6655 -big breasts = \u5de8\u4e73 -breast expansion = \u4e73\u623f\u81a8\u80c0 -breast feeding = \u54fa\u4e73 -breast reduction = \u4e73\u623f\u7f29\u5c0f -gigantic breasts = \u6781\u4e73 -huge breasts = \u8d85\u4e73 -lactation = \u6bcd\u4e73 -milking = \u6324\u5976 -multiple paizuri = \u591a\u91cd\u4e73\u4ea4 -oppai loli = \u5de8\u4e73\u841d\u8389 -paizuri = \u4e73\u4ea4 -clothed paizuri = \u7a7f\u8863\u4e73\u4ea4 -small breasts = \u8d2b\u4e73 -big nipples = \u5927\u4e73\u5934 -dark nipples = \u6697\u8272\u4e73\u5934 -dicknipples = \u9634\u830e\u4e73\u5934 -inverted nipples = \u4e73\u5934\u5185\u9677 -multiple nipples = \u591a\u4e73\u5934 -nipple birth = \u4e73\u5934\u51fa\u4ea7 -nipple expansion = \u4e73\u5934\u81a8\u80c0 -nipple fuck = \u4e73\u7a74\u6027\u4ea4 -navel fuck = \u809a\u8110\u5978 -pregnant = \u6000\u5b55 -stomach deformation = \u8179\u90e8\u53d8\u5f62 -chastity belt = \u8d1e\u64cd\u5e26 -crotch tattoo = \u88c6\u90e8\u7eb9\u8eab -hairy = \u591a\u6bdb -pantyjob = \u5185\u88e4\u4ea4 -pubic stubble = \u9634\u6bdb\u832c -urethra insertion = \u5c3f\u9053\u63d2\u5165 -adventitious penis = \u7578\u4f4d\u9634\u830e -balls expansion = \u777e\u4e38\u751f\u957f -ball sucking = \u5438\u7403 -balljob = \u7403\u4ea4 -big balls = \u5927\u777e\u4e38 -big penis = \u5927\u6839 -dick growth = \u9634\u830e\u751f\u957f -frottage = \u9634\u830e\u6469\u64e6 -horse cock = \u9a6c\u6839 -huge penis = \u5de8\u6839 -multiple penises = \u9e21\u9e21\u590d\u9e21\u9e21 -penis birth = \u9634\u830e\u51fa\u4ea7 -phimosis = \u5305\u830e -prostate massage = \u524d\u5217\u817a\u6309\u6469 -smegma = \u9634\u57a2 -adventitious vagina = \u7578\u4f4d\u9634\u9053 -big clit = \u5927\u9634\u8482 -big vagina = \u5927\u9634\u9053 -birth = \u51fa\u4ea7 -cervix penetration = \u5b50\u5bab\u9888\u7a7f\u900f -clit growth = \u9634\u8482\u751f\u957f -clit insertion = \u9634\u8482\u63d2\u5165 -cunnilingus = \u8214\u9634 -defloration = \u7834\u5904 -multiple vaginas = \u591a\u9634\u9053 -tribadism = \u8d1d\u5408 -anal = \u809b\u4ea4 -anal birth = \u809b\u95e8\u51fa\u4ea7 -ass expansion = \u81c0\u90e8\u81a8\u80c0 -assjob = \u5c3b\u4ea4 -big ass = \u5927\u5c41\u80a1 -enema = \u704c\u80a0 -farting = \u653e\u5c41 -spanking = \u6253\u5c41\u80a1 -tail = \u5c3e\u5df4 -eggs = \u4ea7\u5375 -gaping = \u655e\u53e3 -large insertions = \u5927\u73a9\u5177 -nakadashi = \u4e2d\u51fa -prolapse = \u8131\u5782 -unbirth = \u5165\u9634 -kneepit sex = \u819d\u4e0b\u6027\u4ea4 -leg lock = \u52fe\u817f -legjob = \u817f\u4ea4 -sumata = \u80a1\u95f4\u6027\u4ea4 -denki anma = \u7535\u6c14\u6309\u6469 -foot insertion = \u8db3\u63d2\u5165 -footjob = \u8db3\u4ea4 -sockjob = \u889c\u4ea4\ud83e\udde6 -apron = \u56f4\u88d9 -bandages = \u7ef7\u5e26 -vaginal sticker = \u9634\u8d34 -bandaid = \u521b\u53ef\u8d34 -bike shorts = \u81ea\u884c\u8f66\u77ed\u88e4 -bikini = \u6bd4\u57fa\u5c3c\ud83d\udc59 -bloomers = \u5e03\u9c81\u9a6c -bodystocking = \u8fde\u8eab\u889c -bodysuit = \u8fde\u4f53\u7d27\u8eab\u8863 -bride = \u5a5a\u7eb1 -business suit = \u897f\u88c5 -butler = \u7ba1\u5bb6 -cashier = \u6536\u94f6\u5458 -cheerleader = \u5566\u5566\u961f\u5458 -chinese dress = \u5510\u88c5 -christmas = \u5723\u8bde\u88c5\ud83e\udd36 -clothed male nude female = \u88f8\u5973 -clown = \u5c0f\u4e11\ud83e\udd21 -condom = \u907f\u5b55\u5957 -corset = \u7d27\u8eab\u5185\u8863 -cosplaying = Cosplay -crossdressing = \u5f02\u6027\u88c5 -diaper = \u5c3f\u5e03 -dougi = \u7ec3\u529f\u670d\ud83e\udd4b -fishnets = \u6e14\u7f51 -fundoshi = \u516d\u5c3a\u890c -garter belt = \u540a\u889c\u5e26 -gothic lolita = \u54e5\u7279\u841d\u8389\u88c5 -gymshorts = \u8fd0\u52a8\u77ed\u88e4 -haigure = \u9ad8\u53c9\u88c5 -hijab = \u5934\u5dfe -hotpants = \u70ed\u88e4 -kigurumi = \u5168\u8eab\u5957\u88c5 -kimono = \u548c\u670d\ud83d\udc58 -kindergarten uniform = \u5e7c\u513f\u56ed\u5236\u670d -kunoichi = \u5973\u5fcd\u88c5 -lab coat = \u767d\u5927\u8902 -latex = \u4e73\u80f6\u7d27\u8eab\u8863 -leotard = \u7d27\u8eab\u8863 -lingerie = \u60c5\u8da3\u5185\u8863 -living clothes = \u751f\u7269\u8863 -magical girl = \u9b54\u6cd5\u5c11\u5973 -maid = \u5973\u4ec6\u88c5 -mecha girl = \u673a\u5a18 -metal armor = \u91d1\u5c5e\u76d4\u7532 -miko = \u5deb\u5973\u88c5 -military = \u519b\u88c5 -nazi = \u7eb3\u7cb9\u519b\u88c5 -nun = \u4fee\u5973\u670d -nurse = \u62a4\u58eb\u88c5 -pantyhose = \u8fde\u88e4\u889c -pasties = \u4e73\u8d34 -piercing = \u7a7f\u5b54 -pirate = \u6d77\u76d7\u670d -policewoman = \u8b66\u670d -ponygirl = \u5c0f\u9a6c\u5973 -race queen = \u8d5b\u8f66\u5973\u90ce -randoseru = \u4e66\u5305 -sarashi = \u7f20\u80f8\u5e03 -schoolboy uniform = \u7537\u751f\u5236\u670d -schoolgirl uniform = \u5973\u751f\u5236\u670d -scrotal lingerie = \u9634\u56ca\u888b -small penis = \u5c0f\u5c0f\u9e1f -shimapan = \u6761\u7eb9\u80d6\u6b21 -stewardess = \u7a7a\u59d0\u670d -steward = \u7537\u7a7a\u4e58\u670d -stockings = \u957f\u7b52\u889c -swimsuit = \u6cf3\u88c5 -school swimsuit = \u6b7b\u5e93\u6c34 -sundress = \u590f\u88c5 -thigh high boots = \u9ad8\u7b52\u9774 -tiara = \u5b9d\u51a0 -tights = \u7d27\u8eab\u670d -tracksuit = \u8fd0\u52a8\u670d -waiter = \u7537\u4f8d\u8005\u88c5 -waitress = \u5973\u4f8d\u8005\u88c5 -wet clothes = \u6e7f\u8eab -witch = \u5973\u5deb\u88c5 -double anal = \u53cc\u63d2\u809b\u95e8 -double vaginal = \u53cc\u63d2\u9634\u9053 -fft threesome = \u5973\u5973\u6276 -group = \u4e71\u4ea4 -harem = \u540e\u5bab -layer cake = \u5939\u5fc3\u86cb\u7cd5 -oyakodon = \u6bcd\u5973\u4e3c -triple anal = \u4e09\u63d2\u809b\u95e8 -triple vaginal = \u4e09\u63d2\u9634\u9053 -ttf threesome = \u6276\u6276\u5973 -twins = \u53cc\u80de\u80ce -all the way through = \u6d88\u5316\u9053\u8d2f\u7a7f -double penetration = \u53cc\u91cd\u63d2\u5165 -triple penetration = \u4e09\u91cd\u63d2\u5165 -clamp = \u5939\u5177 -glory hole = \u5bfb\u6b22\u6d1e -machine = \u673a\u68b0\u5978 -onahole = \u98de\u673a\u676f -pillory = \u67b7\u5177 -pole dancing = \u94a2\u7ba1\u821e -real doll = \u5145\u6c14\u5a03\u5a03 -sex toys = \u6027\u73a9\u5177 -speculum = \u6269\u5f20\u5668 -strap-on = \u7a7f\u6234\u5f0f\u9633\u5177 -syringe = \u6ce8\u5c04\u5668 -tail plug = \u5c3e\u585e -tube = \u63d2\u7ba1 -vacbed = \u771f\u7a7a\u5e8a -whip = \u97ad\u6253 -wooden horse = \u6728\u9a6c -wormhole = \u866b\u6d1e -oil = \u6cb9 -underwater = \u6c34\u4e0b -blood = \u6d41\u8840 -squirting = \u6f6e\u5439 -bukkake = \u7cbe\u6db2\u8986\u76d6 -cum bath = \u7cbe\u6db2\u6d74 -cum swap = \u4ea4\u6362\u7cbe\u6db2 -low scat = \u672a\u901a\u8fc7\u6392\u4fbf -menstruation = \u7ecf\u8840 -omorashi = \u6f0f\u5c3f -public use = \u8089\u4fbf\u5668 -scat = \u6392\u4fbf\ud83d\udca9 -sweating = \u51fa\u6c57 -urination = \u6392\u5c3f -chikan = \u75f4\u6c49 -rape = \u5f3a\u5978 -bdsm = \u8c03\u6559 -femdom = \u5973\u6027\u4e3b\u5bfc -forniphilia = \u4eba\u4f53\u5bb6\u5177 -human cattle = \u4eba\u7c7b\u9972\u517b -human pet = \u4eba\u5ba0 -orgasm denial = \u9ad8\u6f6e\u7981\u6b62 -slave = \u5974\u96b6 -tickling = \u6320\u75d2 -bondage = \u675f\u7f1a -harness = \u633d\u5177 -shibari = \u6346\u7ed1 -stuck in wall = \u5361\u5728\u5899\u4e0a -abortion = \u5815\u80ce -cannibalism = \u98df\u4eba -catfight = \u732b\u6597 -cbt = CBT -dismantling = \u62c6\u89e3 -guro = \u80a2\u89e3 -electric shocks = \u7535\u51fb -ryona = \u54c0\u568e -snuff = \u6740\u5bb3 -torture = \u62f7\u6253 -trampling = \u8df5\u8e0f -wrestling = \u6454\u89d2 -autofellatio = \u81ea\u5439 -autopaizuri = \u81ea\u4e73\u4ea4 -masturbation = \u81ea\u6170 -phone sex = \u7535\u8bdd\u6027\u7231 -selfcest = \u81ea\u4ea4 -solo action = \u81ea\u6478 -table masturbation = \u684c\u89d2\u81ea\u6170 -blind = \u5931\u660e -handicapped = \u6b8b\u75be -mute = \u54d1\u5df4 -futanari = \u6276\u5979 -gender bender = \u6027\u8f6c\u6362 -shemale = \u4eba\u5996\u2642 -bisexual = \u53cc\u6027 -dickgirl on dickgirl = \u6276\u4e0a\u6276 -male on dickgirl = \u7537\u4e0a\u6276 -first person perspective = \u7b2c\u4e00\u4eba\u79f0\u89c6\u89d2 -x-ray = \u900f\u89c6 -blackmail = \u52d2\u7d22 -coach = \u6559\u7ec3 -impregnation = \u53d7\u5b55 -prostitution = \u63f4\u4ea4 -teacher = \u6559\u5e08 -tomboy = \u5047\u5c0f\u5b50 -tutor = \u5bb6\u5ead\u6559\u5e08 -vtuber = \u865a\u62df\u4e3b\u64ad -widow = \u5be1\u5987 -yandere = \u75c5\u5a07 -yuri = \u767e\u5408 -dickgirls only = \u7eaf\u6276\u5979 -females only = \u7eaf\u5973\u6027\u26a2 -sole dickgirl = \u5355\u6276\u5979 -sole female = \u5355\u5973\u4e3b -cheating = \u51fa\u8f68 -netorare = NTR -swinging = \u6362\u59bb -aunt = \u963f\u59e8 -cousin = \u8868\u59d0\u59b9 -daughter = \u5973\u513f -granddaughter = \u5b59\u5973 -grandmother = \u7956\u6bcd -incest = \u4e71\u4f26 -inseki = \u59fb\u4eb2 -mother = \u6bcd\u4eb2 -niece = \u4f84\u5973 -sister = \u59d0\u59b9 -exhibitionism = \u9732\u9634\u7656 -filming = \u6444\u50cf -hidden sex = \u9690\u853d\u6027\u4ea4 -humiliation = \u5c48\u8fb1 -voyeurism = \u5077\u7aa5 - -[misc] -yukkuri = \u6cb9\u5e93\u91cc -animal on animal = \u517d\u517d -body swap = \u6362\u8eab -frottage = \u9634\u830e\u6469\u64e6 -ffm threesome = \u5973\u7537\u5973 -group = \u4e71\u4ea4 -mmf threesome = \u7537\u5973\u7537 -mmt threesome = \u7537\u6276\u7537 -mtf threesome = \u7537\u6276\u5973 -oyakodon = \u4eb2\u5b50\u4e3c -ttm threesome = \u6276\u6276\u7537 -twins = \u53cc\u80de\u80ce -dakimakura = \u62b1\u6795 -time stop = \u65f6\u95f4\u505c\u6b62\u23f1\ufe0f -3d = 3D -anaglyph = \u7ea2\u84dd3D -animated = \u52a8\u56fe -anthology = \u9009\u96c6 -artbook = \u753b\u96c6 -comic = \u8fde\u73af\u6f2b\u753b -figure = \u624b\u529e -full color = \u5168\u5f69\u8272 -game sprite = \u50cf\u7d20\u753b -how to = \u6559\u7a0b -multi-work series = \u7cfb\u5217\u4f5c\u54c1 -novel = \u5c0f\u8bf4 -paperchild = \u7eb8\u7247\u4eba -redraw = \u91cd\u7ed8 -screenshots = \u622a\u56fe -stereoscopic = \u7acb\u4f53\u56fe -story arc = \u6545\u4e8b\u7ebf -tankoubon = \u5355\u884c\u672c -themeless = \u65e0\u4e3b\u9898 -variant set = \u53d8\u4f53\u96c6 -webtoon = Webtoon -western cg\u200e = \u897f\u65b9CG\u96c6 -western non-h = \u897f\u65b9\u65e0H -western imageset = \u897f\u65b9\u56fe\u7247\u96c6 -uncensored = \u65e0\u4fee\u6b63 -mosaic censorship = \u9a6c\u8d5b\u514b\u4fee\u6b63 -full censorship = \u5b8c\u5168\u4fee\u6b63 -hardcore = \u786c\u6838 -non-nude = \u65e0\u88f8\u4f53 -already uploaded = \u5df2\u4e0a\u4f20 -compilation = \u91cd\u590d -forbidden content = \u7981\u6b62\u5185\u5bb9 -realporn = \u771f\u4eba\u8272\u60c5 -replaced = \u5df2\u66ff\u6362 -watermarked = \u6c34\u5370 -incomplete = \u7f3a\u9875 -missing cover = \u7f3a\u5c01\u9762 -out of order = \u987a\u5e8f\u9519\u4e71 -sample = \u6837\u672c -scanmark = \u6c34\u5370 -caption = \u8bf4\u660e\u6587\u5b57 -poor grammar = \u6e23\u7ffb -nudity only = \u4ec5\u88f8\u4f53 -no penetration = \u65e0\u63d2\u5165 -incest = \u4e71\u4f26 -inseki = \u59fb\u4eb2 -thumbelina = \u62c7\u6307\u59d1\u5a18 - diff --git a/mirai-demos/mirai-demo-java/build.gradle b/mirai-demos/mirai-demo-java/build.gradle deleted file mode 100644 index e5e8fc5a1..000000000 --- a/mirai-demos/mirai-demo-java/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -apply plugin: "java" -apply plugin: "kotlin" - -dependencies { - implementation files("../../mirai-core/build/classes/kotlin/jvm/main") // IDE bug - - implementation files("../../mirai-core-qqandroid/build/classes/kotlin/jvm/main") // IDE bug - implementation project(":mirai-core-qqandroid") - implementation project(":mirai-japt") -} - -tasks.withType(JavaCompile) { - options.encoding = "UTF-8" -} - -compileJava.options.encoding = 'UTF-8' - -compileTestJava.options.encoding = 'UTF-8' \ No newline at end of file diff --git a/mirai-demos/mirai-demo-java/src/main/java/demo/BlockingTest.java b/mirai-demos/mirai-demo-java/src/main/java/demo/BlockingTest.java deleted file mode 100644 index b2f8614cf..000000000 --- a/mirai-demos/mirai-demo-java/src/main/java/demo/BlockingTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package demo; - -import net.mamoe.mirai.japt.BlockingBot; -import net.mamoe.mirai.japt.BlockingContacts; -import net.mamoe.mirai.japt.BlockingQQ; -import net.mamoe.mirai.japt.Events; -import net.mamoe.mirai.message.GroupMessage; - -class BlockingTest { - - public static void main(String[] args) throws InterruptedException { - BlockingBot bot = BlockingBot.newInstance(123456, ""); - - bot.login(); - - bot.getFriendList().forEach(friend -> { - System.out.println(friend.getNick()); - }); - - Events.subscribeAlways(GroupMessage.class, (GroupMessage message) -> { - final BlockingQQ sender = BlockingContacts.createBlocking(message.getSender()); - - sender.sendMessage("Hello"); - }); - - Thread.sleep(999999999); - } -} diff --git a/mirai-japt/README.md b/mirai-japt/README.md deleted file mode 100644 index a9627a246..000000000 --- a/mirai-japt/README.md +++ /dev/null @@ -1,9 +0,0 @@ - -# mirai-japt - -Mirai Java Apt - -提供一些阻塞/异步/RxJava API 来让 Java 调用 Mirai 的挂起函数 API 更容易 -提供 Utils 类来让 Java 调用 Mirai 的内联方法更容易 - -该模块暂未完成. \ No newline at end of file diff --git a/mirai-japt/build.gradle.kts b/mirai-japt/build.gradle.kts deleted file mode 100644 index f77859326..000000000 --- a/mirai-japt/build.gradle.kts +++ /dev/null @@ -1,53 +0,0 @@ -plugins { - kotlin("jvm") - java -} - -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 serializationVersion: String by rootProject.ext - -val klockVersion: String by rootProject.ext -val ktorVersion: String by rootProject.ext - -description = "Java helper for Mirai" - -@Suppress("PropertyName") -val mirai_japt_version: String by rootProject.ext -version = mirai_japt_version - -kotlin { - sourceSets { - all { - languageSettings.enableLanguageFeature("InlineClasses") - - languageSettings.useExperimentalAnnotation("kotlin.Experimental") - } - } -} - -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")) - runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE - - api(group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-javafx", version = "1.3.2") - - api(kotlin("stdlib", kotlinVersion)) - api(kotlinx("io-jvm", kotlinXIoVersion)) - api(kotlinx("io", kotlinXIoVersion)) - api(kotlinx("coroutines-io", coroutinesIoVersion)) - api(kotlinx("coroutines-core", coroutinesVersion)) - api(kotlin("stdlib-jdk7", kotlinVersion)) - api(kotlin("stdlib-jdk8", kotlinVersion)) -} - -tasks.withType<JavaCompile>() { - options.encoding = "UTF-8" -} \ No newline at end of file diff --git a/mirai-japt/mirai-japt.postfixTemplates b/mirai-japt/mirai-japt.postfixTemplates deleted file mode 100644 index 030b8a3d7..000000000 --- a/mirai-japt/mirai-japt.postfixTemplates +++ /dev/null @@ -1,5 +0,0 @@ -## Contact ## -.blocking : Blocking wrap - net.mamoe.mirai.contact.QQ → net.mamoe.mirai.japt.BlockingContacts.createBlocking($expr$) - net.mamoe.mirai.contact.QQ [net.mamoe.mirai.japt.BlockingContacts] → net.mamoe.mirai.japt.BlockingContacts.createBlocking($expr$) - net.mamoe.mirai.timpc.network.QQImpl [net.mamoe.mirai.japt.BlockingContacts] → net.mamoe.mirai.japt.BlockingContacts.createBlocking($expr$) \ No newline at end of file diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java deleted file mode 100644 index fb55493a6..000000000 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingBot.java +++ /dev/null @@ -1,188 +0,0 @@ -package net.mamoe.mirai.japt; - -import kotlinx.io.core.ByteReadPacket; -import net.mamoe.mirai.Bot; -import net.mamoe.mirai.BotAccount; -import net.mamoe.mirai.BotFactoryJvmKt; -import net.mamoe.mirai.contact.QQ; -import net.mamoe.mirai.data.AddFriendResult; -import net.mamoe.mirai.data.GroupInfo; -import net.mamoe.mirai.data.MemberInfo; -import net.mamoe.mirai.message.data.Image; -import net.mamoe.mirai.network.BotNetworkHandler; -import net.mamoe.mirai.utils.BotConfiguration; -import net.mamoe.mirai.utils.MiraiExperimentalAPI; -import net.mamoe.mirai.utils.MiraiInternalAPI; -import net.mamoe.mirai.utils.MiraiLogger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.OutputStream; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.stream.Stream; - -/** - * 对 {@link Bot} 的阻塞式包装 - * - * @see Bot - */ -@SuppressWarnings("unused") -public interface BlockingBot { - /** - * 使用默认配置创建一个机器人实例 - * - * @param id qq 号 - * @param password 密码 - * @return 机器人实例 - */ - static BlockingBot newInstance(long id, String password) { - return BlockingContacts.createBlocking(BotFactoryJvmKt.Bot(id, password)); - } - - /** - * 使用特定配置创建一个机器人实例 - * - * @param id qq 号 - * @param password 密码 - * @return 机器人实例 - */ - static BlockingBot newInstance(long id, String password, BotConfiguration configuration) { - return BlockingContacts.createBlocking(BotFactoryJvmKt.Bot(id, password, configuration)); - } - - // 要获取 Bot 实例列表, 请前往 BotKt - - /** - * 账号信息 - */ - @MiraiInternalAPI - @NotNull - BotAccount getAccount(); - - /** - * QQ 号码. 实际类型为 uint - */ - long getUin(); - - /** - * 获取昵称 - */ - @NotNull - @MiraiExperimentalAPI(message = "还未支持") - String getNick(); - - /** - * 日志记录器 - */ - @NotNull - MiraiLogger getLogger(); - - // region contacts - - /** - * 获取自身 QQ 实例 - */ - @NotNull - QQ getSelfQQ(); - - /** - * 与这个机器人相关的 QQ 列表. 机器人与 QQ 不一定是好友 - */ - @NotNull - List<BlockingQQ> getFriendList(); - - /** - * 获取缓存的 QQ 对象. 若没有对应的缓存, 则会线程安全地创建一个. - */ - @NotNull - BlockingQQ getFriend(long id); - - /** - * 与这个机器人相关的群列表. 机器人不一定是群成员. - */ - @NotNull - List<BlockingGroup> getGroupList(); - - /** - * 获取缓存的群对象. 若没有对应的缓存, 则会线程安全地创建一个. - * 若 {@code id} 无效, 将会抛出 {@link NoSuchElementException} - */ - @NotNull - BlockingGroup getGroup(long id); - - - // endregion - - // region network - - /** - * 网络模块 - */ - @NotNull - BotNetworkHandler getNetwork(); - - /** - * 登录. - */ - void login(); - - /** - * 查询群列表. 返回值前 32 bits 为 uin, 后 32 bits 为 groupCode - */ - @NotNull - Stream<Long> queryGroupList(); - - /** - * 查询群资料. 获得的仅为当前时刻的资料. - * 请优先使用 {@link #getGroup(long)} 然后查看群资料. - */ - @NotNull - GroupInfo queryGroupInfo(long groupCode); - - /** - * 查询群成员列表. - * 请优先使用 {@link #getGroup(long)} , {@link BlockingGroup#getMembers()} 查看群成员. - * <p> - * 这个函数很慢. 请不要频繁使用. - */ - @NotNull - Stream<MemberInfo> queryGroupMemberList(long groupUin, long groupCode, long ownerId); - - // endregion - - // region actions - - @NotNull - byte[] downloadAsByteArray(@NotNull Image image); - - @NotNull - ByteReadPacket download(@NotNull Image image); - - /** - * 下载图片到 {@code outputStream}. - * <p> - * 不会自动关闭 {@code outputStream} - */ - void download(@NotNull Image image, @NotNull OutputStream outputStream); - - /** - * 添加一个好友 - * - * @param message 若需要验证请求时的验证消息. - * @param remark 好友备注 - */ - @NotNull - AddFriendResult addFriend(long id, @Nullable String message, @Nullable String remark); - - /** - * 同意来自陌生人的加好友请求 - */ - void approveFriendAddRequest(long id, @Nullable String remark); - // endregion - - /** - * 关闭这个 [Bot], 停止一切相关活动. 不可重新登录. - */ - void dispose(@Nullable Throwable throwable); -} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java deleted file mode 100644 index 26faed221..000000000 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContact.java +++ /dev/null @@ -1,87 +0,0 @@ -package net.mamoe.mirai.japt; - -import net.mamoe.mirai.Bot; -import net.mamoe.mirai.contact.Contact; -import net.mamoe.mirai.contact.Member; -import net.mamoe.mirai.contact.QQ; -import net.mamoe.mirai.event.events.BeforeImageUploadEvent; -import net.mamoe.mirai.event.events.EventCancelledException; -import net.mamoe.mirai.event.events.ImageUploadEvent; -import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent; -import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent; -import net.mamoe.mirai.message.data.Image; -import net.mamoe.mirai.message.data.Message; -import net.mamoe.mirai.message.data.MessageChain; -import net.mamoe.mirai.utils.ExternalImage; -import org.jetbrains.annotations.NotNull; - -/** - * 对 {@link Contact} 的阻塞式包装. - */ -@SuppressWarnings("unused") -public interface BlockingContact { - /** - * 这个联系人所属 {@link Bot} - */ - @NotNull - BlockingBot getBot(); - - /** - * 可以是 QQ 号码或者群号码. - * <p> - * 对于 QQ, {@code uin} 与 {@code id} 是相同的意思. - * 对于 Group, {@code groupCode} 与 {@code id} 是相同的意思. - */ - long getId(); - - /** - * 向这个对象发送消息. - * - * @throws EventCancelledException 当发送消息事件被取消 - * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 - * @see FriendMessageSendEvent 发送好友信息事件, cancellable - * @see GroupMessageSendEvent 发送群消息事件. cancellable - */ - // kotlin bug - void sendMessage(@NotNull MessageChain messages) throws EventCancelledException, IllegalStateException; - - /** - * 向这个对象发送消息. - * - * @throws EventCancelledException 当发送消息事件被取消 - * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 - * @see FriendMessageSendEvent 发送好友信息事件, cancellable - * @see GroupMessageSendEvent 发送群消息事件. cancellable - */ - void sendMessage(@NotNull String message) throws EventCancelledException, IllegalStateException; - - /** - * 向这个对象发送消息. - * - * @throws EventCancelledException 当发送消息事件被取消 - * @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出 - * @see FriendMessageSendEvent 发送好友信息事件, cancellable - * @see GroupMessageSendEvent 发送群消息事件. cancellable - */ - void sendMessage(@NotNull Message message) throws EventCancelledException, IllegalStateException; - - /** - * 上传一个图片以备发送. - * 群图片与好友图片在服务器上是通用的, 在 mirai 目前不通用. - * - * @throws EventCancelledException 当发送消息事件被取消 - * @see BeforeImageUploadEvent 图片发送前事件, cancellable - * @see ImageUploadEvent 图片发送完成事件 - */ - Image uploadImage(@NotNull ExternalImage image) throws EventCancelledException; - - /** - * 判断 {@code this} 和 {@code other} 是否是相同的类型, 并且 {@link Contact#getId()} 相同. - * <p> - * 注: - * {@link Contact#getId()} 相同的 {@link Member} 和 {@link QQ}, 他们并不 equals. - * 因为, {@link Member} 含义为群员, 必属于一个群. - * 而 {@link QQ} 含义为一个独立的人, 可以是好友, 也可以是陌生人. - */ - boolean equals(Object other); -} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java deleted file mode 100644 index 2b95d9453..000000000 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingContacts.java +++ /dev/null @@ -1,38 +0,0 @@ -package net.mamoe.mirai.japt; - -import net.mamoe.mirai.Bot; -import net.mamoe.mirai.contact.Group; -import net.mamoe.mirai.contact.Member; -import net.mamoe.mirai.contact.QQ; -import net.mamoe.mirai.japt.internal.BlockingBotImpl; -import net.mamoe.mirai.japt.internal.BlockingGroupImpl; -import net.mamoe.mirai.japt.internal.BlockingMemberImpl; -import net.mamoe.mirai.japt.internal.BlockingQQImpl; -import org.jetbrains.annotations.NotNull; - -import java.util.Objects; - -/** - * 构造阻塞式的联系人. - */ -public final class BlockingContacts { - @NotNull - public static BlockingQQ createBlocking(@NotNull QQ qq) { - return new BlockingQQImpl(Objects.requireNonNull(qq)); - } - - @NotNull - public static BlockingGroup createBlocking(@NotNull Group group) { - return new BlockingGroupImpl(Objects.requireNonNull(group)); - } - - @NotNull - public static BlockingMember createBlocking(@NotNull Member member) { - return new BlockingMemberImpl(Objects.requireNonNull(member)); - } - - @NotNull - public static BlockingBot createBlocking(@NotNull Bot bot) { - return new BlockingBotImpl(Objects.requireNonNull(bot)); - } -} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java deleted file mode 100644 index 2ebd58f6b..000000000 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingGroup.java +++ /dev/null @@ -1,183 +0,0 @@ -package net.mamoe.mirai.japt; - -import net.mamoe.mirai.contact.*; -import net.mamoe.mirai.data.MemberInfo; -import net.mamoe.mirai.event.events.*; -import net.mamoe.mirai.utils.MiraiExperimentalAPI; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.List; -import java.util.NoSuchElementException; - -@SuppressWarnings("unused") -public interface BlockingGroup extends BlockingContact { - /** - * 群名称. - */ - @NotNull - String getName(); - - /** - * 修改群名称 - * 频繁修改可能会被服务器拒绝. - * - * @throws PermissionDeniedException 无权限修改时将会抛出异常 - * @see MemberPermissionChangeEvent - */ - void setName(@NotNull String name) throws PermissionDeniedException; - - /** - * 入群公告, 没有时为空字符串. (同步事件更新) - */ - @NotNull - String getEntranceAnnouncement(); - - /** - * 修改入群公告. - * - * @throws PermissionDeniedException 无权限修改时将会抛出异常 - * @see GroupEntranceAnnouncementChangeEvent - */ - void setEntranceAnnouncement(@NotNull String announcement) throws PermissionDeniedException; - - /** - * 获取全员禁言状态 - * - * @return 全员禁言状态. true 为开启 - */ - boolean isMuteAll(); - - /** - * 设置全体禁言 - * - * @see GroupMuteAllEvent - */ - void setMuteAll(boolean enabled) throws PermissionDeniedException; - - /** - * 获取坦白说状态 - * - * @return 坦白说状态, true 为允许 - */ - boolean isConfessTalkEnabled(); - - /** - * 设置坦白说状态 - * - * @throws PermissionDeniedException 无权限修改时将会抛出异常 - * @see GroupAllowConfessTalkEvent - */ - void setConfessTalk(boolean enabled) throws PermissionDeniedException; - - /** - * 获取允许群员邀请好友入群的状态. - * - * @return 允许群员邀请好友入群的状态. `true` 为允许 - */ - boolean isAllowMemberInvite(); - - /** - * 设置允许群员邀请好友入群的状态. - * - * @throws PermissionDeniedException 无权限修改时将会抛出异常 - * @see GroupAllowMemberInviteEvent - */ - void setAllowMemberInvite(boolean allow) throws PermissionDeniedException; - - /** - * 获取自动加群审批的状态 - */ - boolean isAutoApproveEnabled(); - - /** - * 匿名聊天是否开启 - */ - boolean isAnonymousChatEnabled(); - - /** - * 同为 groupCode, 用户看到的群号码. - */ - @Override - long getId(); - - /** - * 群主 (同步事件更新) - */ - @NotNull - BlockingMember getOwner(); - - /** - * 机器人被禁言还剩余多少秒 - * - * @see BotMuteEvent - * @see GroupKt#isBotMuted - */ - int getBotMuteRemaining(); - - /** - * 检查机器人是否正处于禁言状态 - */ - default boolean isBotMuted() { - int time = getBotMuteRemaining(); - return time != 0 && time != 0xFFFFFFFF; - } - - /** - * 机器人在这个群里的权限 - * - * @see BotGroupPermissionChangeEvent - */ - @NotNull - @MiraiExperimentalAPI - MemberPermission getBotPermission(); - - /** - * 在 {@link Group} 实例创建的时候查询一次. 并与事件同步事件更新 - * <p> - * **注意**: 获得的列表仅为这一时刻的成员列表的镜像. 它将不会被更新 - */ - @NotNull - List<BlockingMember> getMembers(); - - /** - * 获取群成员. 若此 ID 的成员不存在, 则会抛出 {@link NoSuchElementException} - */ - @NotNull - BlockingMember getMember(long id); - - /** - * 获取群成员. 若此 ID 的成员不存在则返回 null - */ - @Nullable - BlockingMember getMemberOrNull(long id); - - /** - * 检查此 id 的群成员是否存在 - */ - boolean containsMember(long id); - - /** - * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 - */ - boolean quit(); - - /** - * 构造一个 [Member]. - * 非特殊情况请不要使用这个函数. 优先使用 [get]. - */ - @MiraiExperimentalAPI(message = "dangerous") - @NotNull - Member newMember(@NotNull MemberInfo memberInfo); - - @NotNull - String toFullString(); - - static long calculateGroupUinByGroupCode(long groupCode) { - return Group.Companion.calculateGroupUinByGroupCode(groupCode); - } - - static long calculateGroupCodeByGroupUin(long groupUin) { - return Group.Companion.calculateGroupCodeByGroupUin(groupUin); - } -} \ No newline at end of file diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java deleted file mode 100644 index 1f02dfeec..000000000 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingMember.java +++ /dev/null @@ -1,90 +0,0 @@ -package net.mamoe.mirai.japt; - -import net.mamoe.mirai.contact.MemberPermission; -import net.mamoe.mirai.contact.PermissionDeniedException; -import net.mamoe.mirai.event.events.MemberCardChangeEvent; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public interface BlockingMember extends BlockingQQ { - /** - * 所在的群 - */ - @NotNull - BlockingGroup getGroup(); - - /** - * 权限 - */ - @NotNull - MemberPermission getPermission(); - - /** - * 群名片. 可能为空. - */ - @NotNull - String getNameCard(); - - /** - * 修改群名片. 将会触发事件 - * - * @throws PermissionDeniedException 无权限修改时 - * @see #getGroupCardOrNick() 获取非空群名片或昵称 - * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件 - */ - void setNameCard(@NotNull String nameCard) throws PermissionDeniedException; - - /** - * 获取群名片或昵称 - */ - @NotNull - default String getGroupCardOrNick() { - String nameCard = this.getNameCard(); - if (!nameCard.isBlank()) { - return nameCard; - } - return this.getNick(); - } - - /** - * 禁言 - * - * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. - * @throws PermissionDeniedException 无权限修改时 - */ - void mute(int durationSeconds); - - /** - * 禁言 - * - * @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. - * @throws PermissionDeniedException 无权限修改时 - */ - default void mute(long durationSeconds) { - mute((int) durationSeconds); - } - - /** - * 解除禁言 - * - * @throws PermissionDeniedException 无权限修改时 - */ - void unmute(); - - /** - * 踢出该成员. - * - * @param message 消息 - * @throws PermissionDeniedException 无权限修改时 - */ - void kick(@NotNull String message); - - /** - * 踢出该成员. - * - * @throws PermissionDeniedException 无权限修改时 - */ - default void kick() { - kick(""); - } -} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java deleted file mode 100644 index 7fb7afc0e..000000000 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/BlockingQQ.java +++ /dev/null @@ -1,50 +0,0 @@ -package net.mamoe.mirai.japt; - -import net.mamoe.mirai.data.FriendNameRemark; -import net.mamoe.mirai.data.PreviousNameList; -import net.mamoe.mirai.data.Profile; -import net.mamoe.mirai.utils.MiraiExperimentalAPI; -import org.jetbrains.annotations.NotNull; - -@SuppressWarnings("unused") -public interface BlockingQQ extends BlockingContact { - /** - * 获取 QQ 号码 - * - * @return QQ 号码 - */ - @Override - long getId(); - - /** - * 获取昵称 - * - * @return 昵称 - */ - String getNick(); - - /** - * 查询用户资料 - */ - @MiraiExperimentalAPI(message = "还未支持") - @NotNull - Profile queryProfile(); - - /** - * 查询曾用名. - * <p> - * 曾用名可能是: - * - 昵称 - * - 共同群内的群名片 - */ - @MiraiExperimentalAPI(message = "还未支持") - @NotNull - PreviousNameList queryPreviousNameList(); - - /** - * 查询机器人账号给这个人设置的备注 - */ - @MiraiExperimentalAPI(message = "还未支持") - @NotNull - FriendNameRemark queryRemark(); -} diff --git a/mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java b/mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java deleted file mode 100644 index ab46c98c6..000000000 --- a/mirai-japt/src/main/java/net/mamoe/mirai/japt/Events.java +++ /dev/null @@ -1,67 +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.japt; - -import kotlinx.coroutines.GlobalScope; -import net.mamoe.mirai.event.Event; -import net.mamoe.mirai.event.Listener; -import net.mamoe.mirai.event.ListeningStatus; -import net.mamoe.mirai.event.internal.EventInternalJvmKt; -import net.mamoe.mirai.japt.internal.EventsImplKt; -import org.jetbrains.annotations.NotNull; - -import java.util.function.Consumer; -import java.util.function.Function; - -/** - * 事件处理 - */ -public final class Events { - - /** - * 监听一个事件, 当 {@code onEvent} 返回 {@link ListeningStatus#STOPPED} 时停止监听. - * 机器人离线后不会停止监听. - * - * @param eventClass 事件类 - * @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听. - * @param <E> 事件类型 - * @return 事件监听器. 可调用 {@link Listener#complete()} 或 {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止. - */ - @NotNull - public static <E extends Event> Listener<E> subscribe(@NotNull Class<E> eventClass, @NotNull Function<E, ListeningStatus> onEvent) { - return EventInternalJvmKt._subscribeEventForJaptOnly(eventClass, GlobalScope.INSTANCE, onEvent); - } - - /** - * 监听一个事件, 直到手动停止. - * 机器人离线后不会停止监听. - * - * @param eventClass 事件类 - * @param onEvent 事件处理. 返回 {@link ListeningStatus#LISTENING} 时继续监听. - * @param <E> 事件类型 - * @return 事件监听器. 可调用 {@link Listener#complete()} 或 {@link Listener#completeExceptionally(Throwable)} 让监听正常停止或异常停止. - */ - @NotNull - public static <E extends Event> Listener<E> subscribeAlways(@NotNull Class<E> eventClass, @NotNull Consumer<E> onEvent) { - return EventInternalJvmKt._subscribeEventForJaptOnly(eventClass, GlobalScope.INSTANCE, onEvent); - } - - /** - * 阻塞地广播一个事件. - * - * @param event 事件 - * @param <E> 事件类型 - * @return {@code event} 本身 - */ - @NotNull - public static <E extends Event> E broadcast(@NotNull E event) { - return EventsImplKt.broadcast(event); - } -} \ No newline at end of file diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt deleted file mode 100644 index 0fbc212b4..000000000 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingBotImpl.kt +++ /dev/null @@ -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 - */ - -package net.mamoe.mirai.japt.internal - -import kotlinx.coroutines.runBlocking -import kotlinx.io.core.ByteReadPacket -import kotlinx.io.core.readBytes -import net.mamoe.mirai.Bot -import net.mamoe.mirai.BotAccount -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.data.AddFriendResult -import net.mamoe.mirai.data.GroupInfo -import net.mamoe.mirai.data.MemberInfo -import net.mamoe.mirai.japt.BlockingBot -import net.mamoe.mirai.japt.BlockingGroup -import net.mamoe.mirai.japt.BlockingQQ -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.network.BotNetworkHandler -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.MiraiLogger -import net.mamoe.mirai.utils.toList -import java.io.OutputStream -import java.util.stream.Stream -import kotlin.streams.asStream - -internal class BlockingBotImpl(private val bot: Bot) : BlockingBot { - @MiraiInternalAPI - override fun getAccount(): BotAccount = bot.account - - override fun getUin(): Long = bot.uin - @MiraiExperimentalAPI - override fun getNick(): String = bot.nick - - override fun getLogger(): MiraiLogger = bot.logger - override fun getSelfQQ(): QQ = bot.selfQQ - - override fun queryGroupMemberList(groupUin: Long, groupCode: Long, ownerId: Long): Stream<MemberInfo> = - runBlocking { bot.queryGroupMemberList(groupUin, groupCode, ownerId) }.asStream() - - @UseExperimental(MiraiInternalAPI::class) - override fun getFriendList(): List<BlockingQQ> = bot.qqs.delegate.toList().map { it.blocking() } - - override fun getFriend(id: Long): BlockingQQ = bot.getFriend(id).blocking() - override fun queryGroupList(): Stream<Long> = runBlocking { bot.queryGroupList() }.asStream() - - @UseExperimental(MiraiInternalAPI::class) - override fun getGroupList(): List<BlockingGroup> = bot.groups.delegate.toList().map { it.blocking() } - - override fun queryGroupInfo(code: Long): GroupInfo = runBlocking { bot.queryGroupInfo(code) } - - override fun getGroup(id: Long): BlockingGroup = runBlocking { bot.getGroup(id).blocking() } - override fun getNetwork(): BotNetworkHandler = bot.network - override fun login() = runBlocking { bot.login() } - override fun downloadAsByteArray(image: Image): ByteArray = bot.run { runBlocking { image.download().readBytes() } } - override fun download(image: Image): ByteReadPacket = bot.run { runBlocking { image.download() } } - override fun download(image: Image, outputStream: OutputStream) = bot.run { runBlocking { image.downloadTo(outputStream) } } - - override fun addFriend(id: Long, message: String?, remark: String?): AddFriendResult = runBlocking { bot.addFriend(id, message, remark) } - override fun approveFriendAddRequest(id: Long, remark: String?) = runBlocking { bot.approveFriendAddRequest(id, remark) } - override fun dispose(throwable: Throwable?) = bot.close(throwable) -} \ No newline at end of file diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContacts.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContacts.kt deleted file mode 100644 index c662df0e7..000000000 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContacts.kt +++ /dev/null @@ -1,27 +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 - */ - -@file:Suppress("NOTHING_TO_INLINE", "unused") - -package net.mamoe.mirai.japt.internal - -import net.mamoe.mirai.Bot -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.japt.* - -inline fun Group.blocking(): BlockingGroup = - BlockingContacts.createBlocking(this) - -inline fun QQ.blocking(): BlockingQQ = BlockingContacts.createBlocking(this) -inline fun Member.blocking(): BlockingMember = - BlockingContacts.createBlocking(this) - -inline fun Bot.blocking(): BlockingBot = BlockingContacts.createBlocking(this) \ No newline at end of file diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt deleted file mode 100644 index 193b57506..000000000 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/BlockingContactsImpl.kt +++ /dev/null @@ -1,130 +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 - */ - -@file:Suppress("EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.japt.internal - -import kotlinx.coroutines.runBlocking -import net.mamoe.mirai.contact.Group -import net.mamoe.mirai.contact.Member -import net.mamoe.mirai.contact.MemberPermission -import net.mamoe.mirai.contact.QQ -import net.mamoe.mirai.data.FriendNameRemark -import net.mamoe.mirai.data.MemberInfo -import net.mamoe.mirai.data.PreviousNameList -import net.mamoe.mirai.data.Profile -import net.mamoe.mirai.japt.BlockingBot -import net.mamoe.mirai.japt.BlockingGroup -import net.mamoe.mirai.japt.BlockingMember -import net.mamoe.mirai.japt.BlockingQQ -import net.mamoe.mirai.message.data.* -import net.mamoe.mirai.utils.ExternalImage -import net.mamoe.mirai.utils.MiraiExperimentalAPI -import net.mamoe.mirai.utils.MiraiInternalAPI -import net.mamoe.mirai.utils.toList - -internal class BlockingQQImpl(private val delegate: QQ) : BlockingQQ { - override fun getBot(): BlockingBot = delegate.bot.blocking() - override fun getId(): Long = delegate.id - override fun getNick(): String = delegate.nick - - override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) } - override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } - override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) } - override fun uploadImage(image: ExternalImage): Image = runBlocking { delegate.uploadImage(image) } - - @MiraiExperimentalAPI - override fun queryProfile(): Profile = runBlocking { delegate.queryProfile() } - - @MiraiExperimentalAPI - override fun queryPreviousNameList(): PreviousNameList = runBlocking { delegate.queryPreviousNameList() } - - @MiraiExperimentalAPI - override fun queryRemark(): FriendNameRemark = runBlocking { delegate.queryRemark() } -} - -internal class BlockingGroupImpl(private val delegate: Group) : BlockingGroup { - override fun sendMessage(messages: MessageChain) = runBlocking { delegate.sendMessage(messages) } - override fun sendMessage(message: String) = runBlocking { delegate.sendMessage(message.toMessage().toChain()) } - override fun sendMessage(message: Message) = runBlocking { delegate.sendMessage(message.toChain()) } - override fun getOwner(): BlockingMember = delegate.owner.blocking() - @MiraiExperimentalAPI - override fun newMember(memberInfo: MemberInfo): Member = delegate.Member(memberInfo) - - override fun uploadImage(image: ExternalImage): Image = runBlocking { delegate.uploadImage(image) } - override fun setEntranceAnnouncement(announcement: String) { - delegate.entranceAnnouncement = announcement - } - - override fun getName(): String = delegate.name - override fun getId(): Long = delegate.id - @MiraiExperimentalAPI - override fun getBotPermission(): MemberPermission = delegate.botPermission - - override fun setConfessTalk(enabled: Boolean) { - delegate.isConfessTalkEnabled = enabled - } - - override fun isAnonymousChatEnabled(): Boolean = delegate.isAnonymousChatEnabled - - override fun isAutoApproveEnabled(): Boolean = delegate.isAutoApproveEnabled - - override fun isConfessTalkEnabled(): Boolean = delegate.isConfessTalkEnabled - - override fun toFullString(): String = delegate.toFullString() - override fun containsMember(id: Long): Boolean = delegate.contains(id) - - override fun isAllowMemberInvite(): Boolean = delegate.isAllowMemberInvite - - override fun getMember(id: Long): BlockingMember = delegate[id].blocking() - override fun getBot(): BlockingBot = delegate.bot.blocking() - override fun getBotMuteRemaining(): Int = delegate.botMuteRemaining - - override fun isMuteAll(): Boolean = delegate.isMuteAll - - override fun setName(name: String) { - delegate.name = name - } - - override fun setMuteAll(enabled: Boolean) { - delegate.isMuteAll = enabled - } - - override fun getEntranceAnnouncement(): String = delegate.entranceAnnouncement - @UseExperimental(MiraiInternalAPI::class) - override fun getMembers(): List<BlockingMember> = - delegate.members.delegate.toList().map { it.blocking() } - - override fun setAllowMemberInvite(allow: Boolean) { - delegate.isAllowMemberInvite = allow - } - - override fun getMemberOrNull(id: Long): BlockingMember? { - return delegate.getOrNull(id)?.blocking() - } - - override fun quit(): Boolean = runBlocking { delegate.quit() } -} - -internal class BlockingMemberImpl(private val delegate: Member) : BlockingMember, BlockingQQ by (delegate as QQ).blocking() { - override fun getGroup(): BlockingGroup = delegate.group.blocking() - override fun getNameCard(): String = delegate.nameCard - - override fun getPermission(): MemberPermission = delegate.permission - override fun setNameCard(nameCard: String) { - delegate.nameCard = nameCard - } - - override fun mute(durationSeconds: Int) = runBlocking { delegate.mute(durationSeconds) } - override fun unmute() = runBlocking { delegate.unmute() } - override fun kick(message: String) { - runBlocking { delegate.kick(message) } - } -} \ No newline at end of file diff --git a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/EventsImpl.kt b/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/EventsImpl.kt deleted file mode 100644 index d2029bb2b..000000000 --- a/mirai-japt/src/main/kotlin/net/mamoe/mirai/japt/internal/EventsImpl.kt +++ /dev/null @@ -1,16 +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.japt.internal - -import kotlinx.coroutines.runBlocking -import net.mamoe.mirai.event.Event -import net.mamoe.mirai.event.broadcast - -internal fun <E : Event> broadcast(e: E): E = runBlocking { e.broadcast() } \ No newline at end of file diff --git a/mirai-plugins/build.gradle b/mirai-plugins/build.gradle deleted file mode 100644 index e69de29bb..000000000 diff --git a/mirai-plugins/image-sender/artifact/mirai-plugin-image-sender.jar b/mirai-plugins/image-sender/artifact/mirai-plugin-image-sender.jar deleted file mode 100644 index 4085cd215..000000000 Binary files a/mirai-plugins/image-sender/artifact/mirai-plugin-image-sender.jar and /dev/null differ diff --git a/mirai-plugins/image-sender/build.gradle.kts b/mirai-plugins/image-sender/build.gradle.kts deleted file mode 100644 index c2c3174a9..000000000 --- a/mirai-plugins/image-sender/build.gradle.kts +++ /dev/null @@ -1,50 +0,0 @@ -plugins { - kotlin("jvm") - java - id("com.github.johnrengelman.shadow") -} - -version = "1.0.0" - -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 serializationVersion: String by rootProject.ext - -val klockVersion: String by rootProject.ext -val ktorVersion: String by rootProject.ext - -kotlin { - sourceSets { - all { - languageSettings.enableLanguageFeature("InlineClasses") - - languageSettings.useExperimentalAnnotation("kotlin.Experimental") - } - } -} - - -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-console")) - runtimeOnly(files("../mirai-core/build/classes/kotlin/jvm/main")) // classpath is not added correctly by IDE - - api(kotlin("stdlib", kotlinVersion)) - api(kotlinx("io-jvm", kotlinXIoVersion)) - api(kotlinx("io", kotlinXIoVersion)) - api(kotlinx("coroutines-io", coroutinesIoVersion)) - api(kotlinx("coroutines-core", coroutinesVersion)) - api("org.jsoup:jsoup:1.12.1") - api(group = "com.alibaba", name = "fastjson", version = "1.2.62") -} - -tasks.withType<JavaCompile>() { - options.encoding = "UTF-8" -} \ No newline at end of file diff --git a/mirai-plugins/image-sender/src/main/java/net/mamoe/mirai/imageplugin/ImageProvider.kt b/mirai-plugins/image-sender/src/main/java/net/mamoe/mirai/imageplugin/ImageProvider.kt deleted file mode 100644 index 4a581384c..000000000 --- a/mirai-plugins/image-sender/src/main/java/net/mamoe/mirai/imageplugin/ImageProvider.kt +++ /dev/null @@ -1,48 +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.imageplugin - -import com.alibaba.fastjson.JSON -import kotlinx.coroutines.* -import net.mamoe.mirai.contact.Contact -import net.mamoe.mirai.message.data.Image -import net.mamoe.mirai.message.uploadAsImage -import org.jsoup.Jsoup - -class ImageProvider { - lateinit var contact: Contact - - // `Deferred<Image?>` causes a runtime ClassCastException - - val image: Deferred<Image> by lazy { - GlobalScope.async { - withTimeoutOrNull(5 * 1000) { - withContext(Dispatchers.IO) { - val result = JSON.parseArray( - Jsoup.connect("https://yande.re/post.json?limit=1&page=${(Math.random() * 10000).toInt()}").ignoreContentType( - true - ).timeout( - 10_0000 - ).get().body().text() - ) - Jsoup.connect(result.getJSONObject(0).getString("jpeg_url")) - .userAgent("Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_7; ja-jp) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27") - .timeout(10_0000) - .ignoreContentType(true) - .maxBodySize(Int.MAX_VALUE) - .execute() - .bodyStream() - } - }?.uploadAsImage(contact) ?: error("Unable to download image|连接这个图站需要你的网络在外网") - } - } - -} - diff --git a/mirai-plugins/image-sender/src/main/java/net/mamoe/mirai/imageplugin/ImageSenderMain.kt b/mirai-plugins/image-sender/src/main/java/net/mamoe/mirai/imageplugin/ImageSenderMain.kt deleted file mode 100644 index 41c4343b8..000000000 --- a/mirai-plugins/image-sender/src/main/java/net/mamoe/mirai/imageplugin/ImageSenderMain.kt +++ /dev/null @@ -1,51 +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.imageplugin - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.GlobalScope -import net.mamoe.mirai.event.events.BotOnlineEvent -import net.mamoe.mirai.event.subscribeAlways -import net.mamoe.mirai.event.subscribeMessages -import net.mamoe.mirai.plugins.PluginBase -import net.mamoe.mirai.utils.MiraiExperimentalAPI - -class ImageSenderMain : PluginBase() { - @ExperimentalCoroutinesApi - @MiraiExperimentalAPI - override fun onEnable() { - logger.info("Image Sender plugin enabled") - GlobalScope.subscribeAlways<BotOnlineEvent> { - logger.info("${this.bot.uin} login succeed, it will be controlled by Image Sender Plugin") - this.bot.subscribeMessages { - - case("at me") { - reply(sender.at() + " ? ") - } - - (contains("image") or contains("图")) { - "图片发送中".reply() - ImageProvider().apply { - this.contact = sender - }.image.await().reply() - } - - } - } - } - - override fun onLoad() { - logger.info("loading...") - } - - override fun onDisable() { - - } -} \ No newline at end of file diff --git a/mirai-plugins/image-sender/src/main/resources/plugin.yml b/mirai-plugins/image-sender/src/main/resources/plugin.yml deleted file mode 100644 index 5a7c94dea..000000000 --- a/mirai-plugins/image-sender/src/main/resources/plugin.yml +++ /dev/null @@ -1,5 +0,0 @@ -name: ImageSender -main: net.mamoe.mirai.imageplugin.ImageSenderMain -version: 1.0.0 -author: mamoe -info: a demo plugin of mirai diff --git a/settings.gradle b/settings.gradle index f161aa955..7dd04ac19 100644 --- a/settings.gradle +++ b/settings.gradle @@ -21,55 +21,8 @@ pluginManagement { rootProject.name = 'mirai' -include(':mirai-demos') - -try { - def keyProps = new Properties() - def keyFile = file("local.properties") - if (keyFile.exists()) keyFile.withInputStream { keyProps.load(it) } - if (!keyProps.getProperty("sdk.dir", "").isEmpty()) { - include(':mirai-demos:mirai-demo-android') - project(':mirai-demos:mirai-demo-android').projectDir = file('mirai-demos/mirai-demo-android') - } else { - println("Android SDK 可能未安装. \n将不会加载模块 `mirai-demo-android`, 但这并不影响其他 demo 的加载 ") - println("Android SDK might not be installed. \nModule `mirai-demo-android` will not be included, but other demos will not be influenced") - } -} catch (Exception e) { - e.printStackTrace() -} - include(':mirai-core') -//include(':mirai-core-timpc') include(':mirai-core-qqandroid') -include(':mirai-japt') -include(':mirai-console') -//include(':mirai-api') -include(':mirai-api-http') -include(':mirai-demos:mirai-demo-1') -include(':mirai-demos:mirai-demo-gentleman') -include(':mirai-demos:mirai-demo-java') -include(':mirai-plugins') -include(':mirai-plugins:image-sender') - -def javaVersion = System.getProperty("java.version") -def versionPos = javaVersion.indexOf(".") -if (versionPos==-1) versionPos = javaVersion.indexOf("-") -if (versionPos==-1){ - println("jdk version unknown") -}else{ - def javaVersionNum = javaVersion.substring(0, versionPos).toInteger() - if (javaVersionNum >= 11) { - println("jdk版本为 "+ javaVersionNum) - //include(':mirai-debug') - } else { - println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 最低需要 JDK 11 才能引入模块 `:mirai-debug`") - } -} - -project(':mirai-demos:mirai-demo-1').projectDir = file('mirai-demos/mirai-demo-1') -project(':mirai-demos:mirai-demo-gentleman').projectDir = file('mirai-demos/mirai-demo-gentleman') -project(':mirai-demos:mirai-demo-java').projectDir = file('mirai-demos/mirai-demo-java') -project(':mirai-plugins:image-sender').projectDir = file('mirai-plugins/image-sender') - +// include(':mirai-api-http') enableFeaturePreview('GRADLE_METADATA') \ No newline at end of file