Merge remote-tracking branch 'origin/dev' into anonymous

This commit is contained in:
Karlatemp 2020-12-19 23:36:09 +08:00
commit 0bb34ee05b
No known key found for this signature in database
GPG Key ID: 21FBDDF664FF06F8
52 changed files with 1508 additions and 2176 deletions

View File

@ -6,8 +6,7 @@ name: Bintray Publish
# events but only for the master branch # events but only for the master branch
on: on:
release: release:
types: types: [created, prereleased]
- created
# A workflow run is made up of one or more jobs that can run sequentially or in parallel # A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs: jobs:
@ -28,31 +27,36 @@ jobs:
run: ./gradlew build # if test's failed, don't publish run: ./gradlew build # if test's failed, don't publish
- name: Check keys - name: Check keys
run: ./gradlew :mirai-core-utils:ensureBintrayAvailable run: >
:mirai-core-api:ensureBintrayAvailable ./gradlew :mirai-core-utils:ensureBintrayAvailable
:mirai-core:ensureBintrayAvailable :mirai-core-api:ensureBintrayAvailable
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} :mirai-core:ensureBintrayAvailable
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-utils:publish - name: Gradle :mirai-core-utils:publish
run: ./gradlew :mirai-core-utils:publish --info run: >
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core-utils:publish --info
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-api:publish - name: Gradle :mirai-core-api:publish
run: ./gradlew :mirai-core-api:publish --info run: >
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core-api:publish --info
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core:publish - name: Gradle :mirai-core:publish
run: ./gradlew :mirai-core:publish --info run: >
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core:publish --info
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-all:bintrayUpload - name: Gradle :mirai-core-all:bintrayUpload
run: ./gradlew :mirai-core-all:bintrayUpload --info run: >
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core-all:bintrayUpload --info
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
# - name: Upload artifact # - name: Upload artifact
# uses: actions/upload-artifact@v1.0.0 # uses: actions/upload-artifact@v1.0.0

View File

@ -29,31 +29,36 @@ jobs:
run: ./gradlew build # if test's failed, don't publish run: ./gradlew build # if test's failed, don't publish
- name: Check keys - name: Check keys
run: ./gradlew :mirai-core-utils:ensureBintrayAvailable run: >-
:mirai-core-api:ensureBintrayAvailable ./gradlew :mirai-core-utils:ensureBintrayAvailable
:mirai-core:ensureBintrayAvailable :mirai-core-api:ensureBintrayAvailable
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} :mirai-core:ensureBintrayAvailable
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-utils:publish - name: Gradle :mirai-core-utils:publish
run: ./gradlew :mirai-core-utils:publish --info run: >-
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core-utils:publish --info
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-api:publish - name: Gradle :mirai-core-api:publish
run: ./gradlew :mirai-core-api:publish --info run: >-
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core-api:publish --info
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core:publish - name: Gradle :mirai-core:publish
run: ./gradlew :mirai-core:publish --info run: >-
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core:publish --info
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
- name: Gradle :mirai-core-all:bintrayUpload - name: Gradle :mirai-core-all:bintrayUpload
run: ./gradlew :mirai-core-all:bintrayUpload run: >-
-Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }} ./gradlew :mirai-core-all:bintrayUpload
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }} -Dbintray_user=${{ secrets.BINTRAY_USER }} -Pbintray_user=${{ secrets.BINTRAY_USER }}
-Dbintray_key=${{ secrets.BINTRAY_KEY }} -Pbintray_key=${{ secrets.BINTRAY_KEY }}
# - name: Upload artifact # - name: Upload artifact
# uses: actions/upload-artifact@v1.0.0 # uses: actions/upload-artifact@v1.0.0

File diff suppressed because it is too large Load Diff

View File

@ -10,8 +10,6 @@ Mirai is designed to handle all sorts of messaging works that can be automatical
## Start ## Start
**Development document** [docs/mirai.md](docs/mirai.md) **Development document** [docs/mirai.md](docs/mirai.md)
[CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md)
### Use as a framework ### Use as a framework
Mirai is able to run as a plugin-supported framework. Mirai is able to run as a plugin-supported framework.

View File

@ -109,84 +109,27 @@ mirai 是一个在全平台下运行,提供 QQ Android 协议支持的高效
## 开始 ## 开始
### 在开始之前,建议你了解一下 Mirai 生态 - 开发文档(新,编写中):[docs](docs/README.md)
[Mirai 生态概览](docs/mirai-ecology.md) - 开发文档(旧):[docs/mirai.md](docs/mirai.md)
- 更新日志: [release](https://github.com/mamoe/mirai/releases)
### 文档
**对于一般使用者, 更建议使用 [Mirai Console](https://github.com/mamoe/mirai-console)。拥有更完善的文档。**
- 快速上手:[quickstart](docs/guide_quick_start.md)
- 开发文档:[docs/mirai.md](docs/mirai.md)
- 常见问题: [docs/FAQ.md](docs/FAQ.md)
- 更新日志: [CHANGELOG](https://github.com/mamoe/mirai/blob/master/CHANGELOG.md) 或 [release](https://github.com/mamoe/mirai/releases)
- 开发计划: [milestones](https://github.com/mamoe/mirai/milestones) - 开发计划: [milestones](https://github.com/mamoe/mirai/milestones)
- 贡献: [CONTRIBUTING](CONTRIBUTING.md) - 贡献: [CONTRIBUTING](CONTRIBUTING.md)
### 使用者
- [mirai-console](https://github.com/mamoe/mirai-console) 支持插件的控制台服务端支持PC和Android平台 **本模块正在开发中**
- [awesome-mirai](https://github.com/project-mirai/awsome-mirai/blob/master/README.md) **mirai相关项目合集** - [awesome-mirai](https://github.com/project-mirai/awsome-mirai/blob/master/README.md) **mirai相关项目合集**
- 常见问题: [docs/FAQ.md](docs/FAQ.md)
#### 从其他平台迁移
- 酷Q的插件可以在 `mirai` 中加载,详见 [Mirai Native](https://github.com/iTXTech/mirai-native)
- 使用 `酷Q HTTP API` 的插件将可以在 `mirai` 中通过`CQHTTP Mirai`加载,详见 [cqhttp-mirai](https://github.com/yyuueexxiinngg/cqhttp-mirai)
### 开发者
开发交流:[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
#### 使用 mirai-console 服务端,为 mirai-console 开发插件
官方支持 SDK 列表:
- `Java``Kotlin` 等 JVM 语言: 为 [mirai-console](https://github.com/mamoe/mirai-console) 直接编写插件并与其他插件开发者合作共享
- `Kotlin Script` [mirai-kts](https://github.com/iTXTech/mirai-kts) 支持使用 `kts` 编写插件,享受 `Kotlin` 带来的一切便利(**仅 OpenJDK 8 以上环境,不支持 Android**
- `C``C++` 等原生语言: [mirai-native](https://github.com/iTXTech/mirai-native) 支持酷 Q 插件在 mirai 上运行 **(仅限 `Windows 32 位 JRE`/支持 `Wine`)**
- `JavaScript` [mirai-js](https://github.com/iTXTech/mirai-js) 支持使用 `JavaScript` 编写插件并**直接**与 mirai 交互
- *Http*:使用由 [mirai-api-http](https://github.com/mamoe/mirai-api-http) 提供的 http 接口进行接入
<details>
<summary>社区支持的 SDK 列表</summary>
基于 `mirai-core` (独立使用):
- `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) 基于 mirai-core 的 Lua SDK并提供了 Java 扩展支持,可在 Lua 中调用 Java 代码开发机器人
基于 `mirai-http-api` (配合 [mirai-console](https://github.com/mamoe/mirai-console)):
- `Python`: [Graia Framework](https://github.com/GraiaProject/Application) 基于 `mirai-api-http` 的机器人开发框架
- `JavaScript`(`Node.js`): [node-mirai](https://github.com/RedBeanN/node-mirai) mirai 的 Node.js SDK
- `Go`: [gomirai](https://github.com/Logiase/gomirai) 基于 mirai-api-http 的 GoLang SDK
- `Mozilla Rhino`: [mirai-rhinojs-sdk](https://github.com/StageGuard/mirai-rhinojs-sdk) 为基于 Rhino(如 Auto.js 等安卓 app 或运行环境)的 JavaScript 提供简单易用的 SDK
- `C++`: [mirai-cpp](https://github.com/cyanray/mirai-cpp) mirai-http-api 的 C++ 封装,方便使用 C++ 开发 mirai-http-api 插件
- `C++`: [miraipp](https://github.com/Chlorie/miraipp-template) mirai-http-api 的另一个 C++ 封装,使用现代 C++ 特性,并提供了较完善的说明文档
- `C#`: [mirai-CSharp](https://github.com/Executor-Cheng/mirai-CSharp) 基于 mirai-api-http 的 C# SDK
- `Rust`: [mirai-rs](https://github.com/HoshinoTented/mirai-rs) mirai-http-api 的 Rust 封装
- `TypeScript`: [mirai-ts](https://github.com/YunYouJun/mirai-ts) mirai-api-http 的 TypeScript SDK附带声明文件拥有良好的注释和类型提示也可作为 JavaScript SDK 使用。
- `易语言`: [e-mirai](https://github.com/only52607/e-mirai) mirai-api-http 的 易语言 SDK使用全中文环境开发插件适合编程新手使用。
- `.Net/C#`: [Hyperai](https://github.com/theGravityLab/ProjHyperai) 从 mirai-api-http 对接到机器人开发框架再到开箱即用的插件式机器人程序一应俱全。
</details>
#### 使用 mirai-core 为第三方依赖库引入项目
Demos: [mirai-demos](https://github.com/mamoe/mirai-demos)
- `Kotlin` 简略版: [mirai Guide - Quick Start](/docs/guide_quick_start.md)
- `Kotlin` 新手版: [mirai Guide - Getting Started](/docs/guide_getting_started.md)
- `Java` 查看上述 Demos
## [贡献](CONTRIBUTING.md) ## [贡献](CONTRIBUTING.md)
我们欢迎一切形式的贡献。 我们欢迎一切形式的贡献。
我们也期待有更多人能加入 mirai 的开发。 我们也期待有更多人能加入 mirai 的开发。
若在使用过程中有任何疑问,可提交 `issue` 或是[邮件联系](mailto:support@mamoe.net). 我们希望 mirai 变得更易用. 若在使用过程中有任何疑问,可提交 [`issue`](https://github.com/mamoe/mirai/issues) 或在 [`Discussions`](https://github.com/mamoe/mirai/discussions) 讨论。 我们希望 mirai 变得更易用.
您的 `star` 是对我们最大的鼓励(点击项目右上角) 您的 `star` 是对我们最大的鼓励(点击项目右上角)
开发交流:[![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
### 加入 mirai 开发组 ### 加入 mirai 开发组
若您有意加入 mirai, mirai-console 和相关社区开发, 请 [邮件联系](mailto:support@mamoe.net) (`support@mamoe.net`), 并附加相关开发经验证明. 若您有意加入 mirai, mirai-console 和相关社区开发, 请 [邮件联系](mailto:support@mamoe.net) (`support@mamoe.net`), 并附加相关开发经验证明.

View File

@ -21,7 +21,7 @@ import org.gradle.api.attributes.Attribute
*/ */
object Versions { object Versions {
const val project = "2.0-M1-dev-3" const val project = "2.0-M1-1"
const val kotlinCompiler = "1.4.21" const val kotlinCompiler = "1.4.21"
const val kotlinStdlib = "1.4.21" const val kotlinStdlib = "1.4.21"

41
docs/Bots.md Normal file
View File

@ -0,0 +1,41 @@
# Mirai - Creating Bots
## 获取 `Bot` 实例
一个机器人被以 `Bot` 对象描述。mirai 的交互入口点是 `Bot`。`Bot` 只可通过 [`BotFactory`](../mirai-core-api/src/commonMain/kotlin/BotFactory.kt#L22-L87) 内的 `newBot` 方法获得:
```kotlin
interface BotFactory {
fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot
fun newBot(qq: Long, password: String): Bot
fun newBot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot
fun newBot(qq: Long, passwordMd5: ByteArray): Bot
companion object : BotFactory by BotFactoryImpl
}
```
通常的调用方法为:
```
// Kotlin
val bot = BotFactory.newBot( )
// Java
Bot bot = BotFactory.INSTANCE.newBot( );
```
### 获取当前所有 `Bot` 实例
在登录后,`Bot` 实例会被自动记录。可在 `Bot.instances` 获取到当前在线的所有 `Bot` 列表。当 `Bot` 离线,其实例就会被删除。
## 登录
创建 `Bot` 后不会自动登录,需要手动调用其 `login()` 方法。在 Kotlin 还可以使用 `Bot.alsoLogin()` 扩展,相当于 `bot.apply { login() }`
### 重新登录
可以再次调用 `Bot.login()` 以重新登录 `Bot`。这一般没有必要——`Bot` 运行时会自动与服务器同步事件(如群成员变化,好友数量变化)。
> 下一步,[Contacts](Contacts.md)
>
> [回到 Mirai 文档索引](README.md)

132
docs/ConfiguringProjects.md Normal file
View File

@ -0,0 +1,132 @@
# Mirai - Configuring Projects
本文介绍如何在一个项目中使用 mirai。
mirai 使用纯 Kotlin 开发,最低要求 `JDK 1.8``Kotlin 1.4`。但注意不要使用 Oracle JDK推荐使用 OpenJDK。
如果你熟悉 Gradle只需要添加 `jcenter` 仓库和依赖 `net.mamoe:mirai-core:VERSION` 即可而不需要继续阅读。下文将详细解释其他方法。
本文提供如下三种配置方法,但更推荐使用 Gradle 构建。
- [A. 使用 Gradle](#a-使用-gradle)
- [B. 使用 Maven](#b-使用-maven)
- [C. 使用 IntelliJ](#c-使用-intellij)
### 选择版本
有关各类版本的区别,参考 [版本规范](Evolution.md#版本规范)
[Version]: https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg?
[Bintray Download]: https://bintray.com/him188moe/mirai/mirai-core/
| 版本类型 | 版本号 |
|:------:|:------------------------------:|
| 稳定 | 1.3.3 |
| 预览 | 2.0-M1-1 |
| 开发 | [![Version]][Bintray Download] |
即使 2.0 还没有稳定,也建议使用 2.0 预览版本,因 1.x 版本将不会收到任何更新。
## A. 使用 Gradle
### Gradle Kotlin DSL
`build.gradle.kts` 添加:
```kotlin
plugins {
kotlin("jvm") version "1.4.21"
}
repositories {
jcenter()
}
dependencies {
api("net.mamoe", "mirai-core", "1.3.3") // 替换为你需要的版本号
}
```
注意,必须添加 Kotlin 插件才能正确获取 mirai 软件包。
### Gradle Groovy DSL
`build.gradle` 添加
```groovy
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.4.21'
}
repositories {
jcenter()
}
dependencies {
api('net.mamoe', 'mirai-core', '1.3.3') // 替换为你需要的版本号
}
```
## B. 使用 Maven
`pom.xml` 中:
### 1. 添加 jcenter 仓库
```xml
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
```
### 2. 添加 mirai 依赖
```xml
<dependencies>
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-core-jvm</artifactId>
<version>1.3.3</version> <!-- 替换版本为你需要的版本 -->
</dependency>
</dependencies>
```
### 3. 添加 Kotlin 依赖
通常 mirai 可以直接使用。但 mirai 使用的 Kotlin 1.4 可能与你的项目使用的其他库依赖的 Kotlin 版本冲突Maven 无法正确处理这种冲突。此时请手动添加 Kotlin 标准库依赖。
```xml
<properties>
<kotlin.version>1.4.20</kotlin.version>
</properties>
```
```xml
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
```
> 可以在 [Kotlin 官方文档](https://www.kotlincn=.net/docs/reference/using-maven.html) 获取更多有关配置 Kotlin 的信息。
## C. 使用 IntelliJ
### 1. 创建项目
使用现有项目,或创建一个新项目(`File->New->Project`)或新模块(`File->New->Module`)。
### 2. 添加依赖
1. 进入 `Project Structure``File->Project Structure``Ctrl+Alt+Shift+S`
2. 进入 `Libraries`
3. 找到 `+` 按钮,点击 `From Maven`
4. 输入 `net.mamoe:mirai-core:1.3.3`,勾选 `Sources`
5. 确认并等待下载
> [回到 Mirai 文档索引](README.md)

11
docs/Contacts.md Normal file
View File

@ -0,0 +1,11 @@
# Mirai - Contacts
[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK2F2YXRhclVybDogU3RyaW5nXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5Db250YWN0T3JCb3Q8LS1Db250YWN0XG5Db250YWN0T3JCb3Q8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PC0tQm90XG5Vc2VyT3JCb3Q8LS1Vc2VyXG5cbkNvbnRhY3Q8LS1Vc2VyXG5Db250YWN0PC0tR3JvdXBcblxuVXNlcjwtLU1lbWJlclxuVXNlcjwtLUZyaWVuZFxuXG5NZW1iZXI8LS1Ob3JtYWxNZW1iZXJcbk1lbWJlcjwtLUFub255bW91c01lbWJlciIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK2F2YXRhclVybDogU3RyaW5nXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5Db250YWN0T3JCb3Q8LS1Db250YWN0XG5Db250YWN0T3JCb3Q8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PC0tQm90XG5Vc2VyT3JCb3Q8LS1Vc2VyXG5cbkNvbnRhY3Q8LS1Vc2VyXG5Db250YWN0PC0tR3JvdXBcblxuVXNlcjwtLU1lbWJlclxuVXNlcjwtLUZyaWVuZFxuXG5NZW1iZXI8LS1Ob3JtYWxNZW1iZXJcbk1lbWJlcjwtLUFub255bW91c01lbWJlciIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)
要主动发送一条消息,调用 `Contact.sendMessage()` 即可。
可通过 `Bot.getFriend``Bot.getGroup` 获取相关对象。
> 下一步,[Events](Events.md)
>
> [回到 Mirai 文档索引](README.md)

333
docs/Events.md Normal file
View File

@ -0,0 +1,333 @@
# Mirai - Events
## 目录
- [事件系统](#事件系统)
- [监听事件](#监听事件)
- [使用 `ListenerHost` 监听事件](#使用-listenerhost-监听事件)
- [在 Kotlin 函数式监听](#在-kotlin-函数式监听)
- [实现事件](#实现事件)
- [工具函数Kotlin](#工具函数kotlin)
## 事件系统
Mirai 以事件驱动,使用者需要监听如 `收到消息``收到入群申请` 等事件。
[`Event`]: ../mirai-core-api/src/commonMain/kotlin/event/Event.kt#L21-L62
每个事件都实现接口 [`Event`],且继承 `AbstractEvent`
实现 `CancellableEvent` 的事件可以被取消(`CancellableEvent.cancel`)。
[事件列表](../mirai-core-api/src/commonMain/kotlin/event/events/README.md#事件)
## 监听事件
有两种方法监听事件:
- [使用 `ListenerHost` 监听事件](#使用-listenerhost-监听事件)
- [在 Kotlin 函数式监听](#在-kotlin-函数式监听)
### 使用 `ListenerHost` 监听事件
标注一个函数方法为事件监听器。mirai 通过反射获取他们并为之注册事件。
> 详见 [EventHandler](../mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt#L27-L168)
#### Kotlin 函数
Kotlin 函数要求:
- 接收者 (英 receiver) 和函数参数: 所标注的 Kotlin 函数必须至少拥有一个接收者或一个函数参数, 或二者都具有. 接收者和函数参数的类型必须相同 (如果二者都存在)
接收者或函数参数的类型都必须为 `Event` 或其子类.
- 返回值: 为 `Unit` 或不指定返回值时将注册为 `CoroutineScope.subscribeAlways`, 为 `ListeningStatus` 时将注册为 `CoroutineScope.subscribe`.
任何其他类型的返回值将会在注册时抛出异常.
所有 Kotlin 非 `suspend` 的函数都将会在 `Dispatchers.IO` 中调用
支持的函数类型:
```kotlin
// 所有函数参数, 函数返回值都不允许标记为可空 (带有 '?' 符号)
// T 表示任何 Event 类型.
suspend fun T.onEvent(T)
suspend fun T.onEvent(T): ListeningStatus
suspend fun T.onEvent(T): Nothing
suspend fun onEvent(T)
suspend fun onEvent(T): ListeningStatus
suspend fun onEvent(T): Nothing
suspend fun T.onEvent()
suspend fun T.onEvent(): ListeningStatus
suspend fun T.onEvent(): Nothing
fun T.onEvent(T)
fun T.onEvent(T): ListeningStatus
fun T.onEvent(T): Nothing
fun onEvent(T)
fun onEvent(T): ListeningStatus
fun onEvent(T): Nothing
fun T.onEvent()
fun T.onEvent(): ListeningStatus
fun T.onEvent(): Nothing
```
Kotlin 使用示例:
- 独立 `CoroutineScope``ListenerHost`
```kotlin
object MyEvents : ListenerHost {
override val coroutineContext = SupervisorJob()
// 可以抛出任何异常, 将在 this.coroutineContext 或 registerEvents 时提供的 CoroutineScope.coroutineContext 中的 CoroutineExceptionHandler 处理.
@EventHandler
suspend fun MessageEvent.onMessage() {
reply("received")
}
}
myCoroutineScope.registerEvents(MyEvents)
```
`onMessage` 抛出的异常将会交给 `myCoroutineScope` 处理
- 合并 `CoroutineScope``ListenerHost`: 使用 `SimpleListenerHost`
```kotlin
object MyEvents : SimpleListenerHost( /* override coroutineContext here */ ) {
override fun handleException(context: CoroutineContext, exception: Throwable) {
// 处理 onMessage 中未捕获的异常
}
@EventHandler
suspend fun MessageEvent.onMessage() { // 可以抛出任何异常, 将在 handleException 处理
reply("received")
// 无返回值 (或者返回 Unit), 表示一直监听事件.
}
@EventHandler
suspend fun MessageEvent.onMessage(): ListeningStatus { // 可以抛出任何异常, 将在 handleException 处理
reply("received")
return ListeningStatus.LISTENING // 表示继续监听事件
// return ListeningStatus.STOPPED // 表示停止监听事件
}
}
MyEvents.registerEvents()
```
#### Java 方法
所有 Java 方法都会在 `Dispatchers.IO` 中调用,因此在 Java 可以调用阻塞方法。
支持的方法类型:
```
// T 表示任何 Event 类型.
void onEvent(T)
Void onEvent(T)
ListeningStatus onEvent(T) // 返回 null 时将抛出异常
```
Java 使用示例:
```java
public class MyEventHandlers extends SimpleListenerHost {
@Override
public void handleException(@NotNull CoroutineContext context, @NotNull Throwable exception){
// 处理事件处理时抛出的异常
}
@EventHandler
public void onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理
event.subject.sendMessage("received");
// 无返回值, 表示一直监听事件.
}
@NotNull
@EventHandler
public ListeningStatus onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理
event.subject.sendMessage("received");
return ListeningStatus.LISTENING; // 表示继续监听事件
// return ListeningStatus.STOPPED; // 表示停止监听事件
}
}
// 注册:
// Events.registerEvents(new MyEventHandlers())
```
### 在 Kotlin 函数式监听
[CoroutineScope.subscribe](../mirai-core-api/src/commonMain/kotlin/event/subscriber.kt#L137-L220)
用法示例:
```kotlin
object MyApplication : CoroutineScope by CoroutineScope(SupervisorJob())
// 启动事件监听器
MyApplication.subscribeAlways<GroupMessageEvent> {
// this: GroupMessageEvent
// it: GroupMessageEvent
// lambda 的 this 和参数都是 GroupMessageEvent
group.sendMessage(sender.at() + "Hello! ${sender.nick}")
}
// YouApplication[Job]!!.cancel() //
```
Mirai 也支持传递函数引用:
```kotlin
suspend fun GroupMessageEvent.onEvent() {
group.sendMessage(sender.at() + "Hello! ${sender.nick}")
}
MyApplication.subscribeAlways<GroupMessageEvent>(GroupMessageEvent::onEvent)
```
既可以使用接收者参数,又可以使用普通参数,还可以同时拥有。如下三个定义都是被接受的:
```kotlin
suspend fun GroupMessageEvent.onEvent()
suspend fun GroupMessageEvent.onEvent(event: GroupMessageEvent)
suspend fun onEvent(event: GroupMessageEvent)
```
### 在 Kotlin 使用 DSL 监听事件
> **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题**
[subscribeMessages](../mirai-core-api/src/commonMain/kotlin/event/subscribeMessages.kt#L37-L64)
示例:
```kotlin
MyApplication.subscribeMessages {
"test" {
// 当消息内容为 "test" 时执行
// this: MessageEvent
reply("test!")
}
"Hello" reply "Hi" // 当消息内容为 "Hello" 时回复 "Hi"
"quote me" quoteReply "ok" // 当消息内容为 "quote me" 时引用该消息并回复 "ok"
"quote me2" quoteReply {
// lambda 也是允许的:
// 返回值接受 Any?
// 为 Unit 时不发送任何内容;
// 为 Message 时直接发送;
// 为 String 时发送为 PlainText
// 否则 toString 并发送为 PlainText
"ok"
}
case("iGNorECase", ignoreCase=true) reply "OK" // 忽略大小写
startsWith("-") reply { cmd ->
// 当消息内容以 "-" 开头时执行
// cmd 为消息去除开头 "-" 的内容
}
val listener: Listener<MessageEvent> = "1" reply "2"
// 每个语句都会被注册为事件监听器,可以这样获取监听器
listener.complete() // 停止 "1" reply "2" 这个事件监听
}
```
## 实现事件
只要实现接口 `Event` 并继承 `AbstractEvent` 的对象就可以被广播。
要广播一个事件,使用 `Event.broadcast()`Kotlin`EventKt.broadcast(Event)`Java
## 工具函数Kotlin
*可能需要较好的 Kotlin 技能才能理解以下内容。*
基于 Kotlin 协程特性mirai 提供 `
### 线性同步(`syncFromEvent`
[linear.kt](../mirai-core-api/src/commonMain/kotlin/event/linear.kt)
挂起协程并获取下一个戳 Bot 的对象:
```kotlin
val target: UserOrBot = syncFromEvent<BotNudgedEvent> { sender }
```
带超时版本:
```kotlin
val target: UserOrBot = syncFromEvent<BotNudgedEvent>(5000) { sender } // 5000ms
```
异步 `async` 版本:
```kotlin
val target: Deferred<UserOrBot> = coroutineScope.asyncFromEvent<BotNudgedEvent> { sender }
```
### 线性同步(`nextEvent`
[nextEvent.kt](../mirai-core-api/src/commonMain/kotlin/event/nextEvent.kt)
挂起协程并获取下一个指定事件:
```kotlin
val event: BotNudgedEvent = nextEvent<BotNudgedEvent>()
```
带超时和过滤器版本:
```kotlin
val event: BotNudgedEvent = nextEvent<BotNudgedEvent>(5000) { it.bot.id == 123456L }
```
### 条件选择(`selectMessages`
> **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题**
[select.kt](../mirai-core-api/src/commonMain/kotlin/event/select.kt)
类似于 Kotlin 协程 `select`mirai 也提供类似的功能。
`selectMessages`:挂起当前协程,等待任意一个事件监听器触发后返回其返回值。
```kotlin
MyCoroutineScope.subscribeAlways<GroupMessageEvent> {
if (message.contentEquals("ocr")) {
reply("请发送你要进行 OCR 的图片或图片链接")
val image: InputStream = selectMessages {
has<Image> { URL(it.queryUrl()).openStream() }
has<PlainText> { URL(it.content).openStream() }
defaultReply { "请发送图片或图片链接" }
timeout(30_000) { event.quoteReply("请在 30 秒内发送图片或图片链接"); null }
} ?: return@subscribeAlways
val result = ocr(image)
quoteReply(result)
}
}
```
这种语法就相当于(伪代码):
```
val image = when (下一条消息) {
包含图片 { 查询图片链接() }
包含纯文本 { 下载图片() }
其他情况 { 引用回复() }
超时 { 引用回复() }
}
```
### 循环条件选择(`whileSelectMessages`
> **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题**
[select.kt](../mirai-core-api/src/commonMain/kotlin/event/select.kt)
类似于 Kotlin 协程 `whileSelect`mirai 也提供类似的功能。
`whileSelectMessages`:挂起当前协程,等待任意一个事件监听器返回 `false` 后返回。
```kotlin
reply("开启复读模式")
whileSelectMessages {
"stop" {
reply("已关闭复读")
false // 停止循环
}
// 也可以使用 startsWith("") { true } 等 DSL
default {
reply(message)
true // 继续循环
}
timeout(3000) {
// on
true
}
} // 等待直到 `false`
reply("复读模式结束")
```
> 下一步,[Messages](Messages.md)
>
> [回到 Mirai 文档索引](README.md)

50
docs/Evolution.md Normal file
View File

@ -0,0 +1,50 @@
# Mirai - Evolution
### Mirai 演进
Mirai 是不断前进的库,将来必定会发生 API 弃用和重构。
维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。
### 版本规范
Mirai 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。
在日常开发中, Mirai 会以 `-dev-1``-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。
在大版本开发过程中Mirai 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。
这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。
在大版本即将发布前Mirai 会以 `-RC` 版本后缀发布最终的预览版本。
`RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。
### 版本选择
**稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。
| 目的 | 推荐至少更新到版本 |
|:------------------:|:--------------:|
| 生产环境 | `x.y.z` |
| 希望尽早体验稳定新特性 | `-RC` |
| 无论如何都想体验新特性 | `-M` |
| 为 Mirai 提交 PR | `-dev` |
## 更新兼容性
对于 `x.y.z` 版本号:
- 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。
- 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。
- 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。
## 弃用周期
一个计划被删除的 API将会在下一个次版本开始经历弃用周期。
如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。
`1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误);
`1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。
`HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。
> [回到 Mirai 文档索引](README.md)

11
docs/Messages.md Normal file
View File

@ -0,0 +1,11 @@
# Mirai - Messages
## 消息系统
在 Contacts 章节提到,要发送消息,使用 `Contact.sendMessage(Message)`
[![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)
`SingleMessage` 表示单个消息元素,`MessageChain`(消息链) 是 `List<SingleMessage>`。主动发送的消息和从服务器接收消息都是 `MessageChain`
mirai 提供大量消息链的扩展:[MessageChain.kt](../mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt#L59)。

View File

@ -4,12 +4,10 @@
## mirai 码 ## mirai 码
mirai 的部分 [消息](../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt) 可以表示为形如 `[mirai:atall]` 的字符串. mirai 的部分 [消息](../mirai-core-api/src/commonMain/kotlin/message/data/Message.kt) 可以表示为形如 `[mirai:atall]` 的字符串.
模块 `mirai-core` 包含消息到 mirai 码的单向转换; 模块 `mirai-serialization` 提供 mirai 码的解析.
运行时: [mirai-serialization](../mirai-serialization/)
## 变更记录 ## 变更记录
- `1.1.0`: 引入 mirai 码于 `mirai-serialization` 模块 - `1.1.0`: 引入 mirai 码于 `mirai-serialization` 模块
- `1.2.0`: mirai 码集成到 mirai-core。不再需要 `mirai-serialization` 模块。
## 格式 ## 格式

71
docs/README.md Normal file
View File

@ -0,0 +1,71 @@
# Mirai
欢迎来到 mirai 文档。
## 生态
[Mirai 生态概览](mirai-ecology.md)
## 确定 SDK
mirai 官方提供 Kotlin/Java 等 JVM 平台语言开发支持。如果你不熟悉该语言,请使用以下社区提供的 SDK
### 基于 [`mirai-console`]
[`mirai-console`]: https://github.com/mamoe/mirai-console
这些 SDK 基于 [`mirai-console`],意味着需要使用 [`mirai-console`] 框架。[`mirai-console`] 也是 mirai 的官方项目之一。
[mamoe/mirai-api-http]: https://github.com/mamoe/mirai-api-http
[iTXTech/mirai-native]: https://github.com/iTXTech/mirai-native
[iTXTech/mirai-js]: https://github.com/iTXTech/mirai-js
[GraiaProject/Application]: https://github.com/GraiaProject/Application
[RedBeanN/node-mirai]: https://github.com/RedBeanN/node-mirai
[Logiase/gomirai]: https://github.com/Logiase/gomirai
[StageGuard/mirai-rhinojs-sdk]: https://github.com/StageGuard/mirai-rhinojs-sdk
[cyanray/mirai-cpp]: https://github.com/cyanray/mirai-cpp
[Chlorie/miraipp]: https://github.com/Chlorie/miraipp-template
[Executor-Cheng/mirai-CSharp]: https://github.com/Executor-Cheng/mirai-CSharp
[HoshinoTented/mirai-rs]: https://github.com/HoshinoTented/mirai-rs
[YunYouJun/mirai-ts]: https://github.com/YunYouJun/mirai-ts
[only52607/e-mirai]: https://github.com/only52607/e-mirai
[theGravityLab/ProjHyperai]: https://github.com/theGravityLab/ProjHyperai
[yyuueexxiinngg/cqhttp-mirai]: https://github.com/yyuueexxiinngg/cqhttp-mirai
| 技术 | 维护者及项目地址 | 描述 |
|:----------------|:--------------------------------------------|:-----------------------------------------------------------------------|
| *Http* | [mamoe/mirai-api-http] | Mirai 官方维护的 HTTP API 插件 |
| `JavaScript` | [iTXTech/mirai-js] | 支持使用 `JavaScript` 编写插件并**直接**与 mirai 交互 |
| `Python` | [Graia Framework][GraiaProject/Application] | 基于 `mirai-api-http` 的机器人开发框架 |
| `Node.js` | [RedBeanN/node-mirai] | mirai 的 Node.js SDK |
| `Go` | [Logiase/gomirai] | 基于 mirai-api-http 的 GoLang SDK |
| `Mozilla Rhino` | [StageGuard/mirai-rhinojs-sdk] | 为基于 Rhino(如 Auto.js 等安卓 app 或运行环境)的 JavaScript 提供简单易用的 SDK |
| `C++` | [cyanray/mirai-cpp] | mirai-http-api 的 C++ 封装,方便使用 C++ 开发 mirai-http-api 插件 |
| `C++` | [Chlorie/miraipp] | mirai-http-api 的另一个 C++ 封装,使用现代 C++ 特性,并提供了较完善的说明文档 |
| `C#` | [Executor-Cheng/mirai-CSharp] | 基于 mirai-api-http 的 C# SDK |
| `Rust` | [HoshinoTented/mirai-rs] | mirai-http-api 的 Rust 封装 |
| `TypeScript` | [YunYouJun/mirai-ts] | mirai-api-http 的 TypeScript SDK附带声明文件拥有良好的注释和类型提示 |
| `易语言` | [only52607/e-mirai] | mirai-api-http 的 易语言 SDK使用全中文环境开发插件适合编程新手使用 |
| `.Net/C#` | [Hyperai][theGravityLab/ProjHyperai] | 从 mirai-api-http 对接到机器人开发框架再到开箱即用的插件式机器人程序一应俱全 |
| *酷 Q 插件* | [iTXTech/mirai-native] | 支持酷 Q 插件在 mirai 上运行 |
| *酷 Q HTTP* | [yyuueexxiinngg/cqhttp-mirai] | 在 mirai-console 开启酷 Q HTTP 服务。 |
### 基于 `mirai-core` 的 SDK
- `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) 基于 mirai-core 的 Lua SDK并提供了 Java 扩展支持,可在 Lua 中调用 Java 代码开发机器人
## 配置 JVM 项目使用 mirai
> 可以首先体验让机器人发送消息:在 IDE 克隆 [mirai-hello-world](https://github.com/project-mirai/mirai-hello-world) 并运行其中入口点。
要把 mirai 作为一个依赖库使用,请参考 [Configuring Projects](ConfiguringProjects.md)。
要使用 mirai-console 框架,请前往 [mirai-console](https://github.com/mamoe/mirai-console)。
## 开发
***本文档正在更新中***
- [Bots](Bots.md)
- [Contacts](Contacts.md)
- [Events](Events.md)

View File

@ -1,186 +0,0 @@
# 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: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:0.23.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
testCompile group: 'junit', name: 'junit', version: '4.12'
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
shadowJar {
// 生成包的命名规则: baseName-version-classifier.jar
manifest {
attributes(
'Main-Class': 'net.mamoe.mirai.simpleloader.MyLoaderKt'
)
}
// 将 build.gradle 打入到 jar 中, 方便查看依赖包版本
from("./"){
include 'build.gradle'
}
}
```

View File

@ -1,131 +0,0 @@
# Mirai Guide - Getting Started
由于Mirai项目在快速推进中因此内容时有变动本文档的最后更新日期为```2020-04-01```,对应版本```0.31.4```
假如仅仅使用Mirai不需要对整个项目进行Clone只需在项目内添加Gradle Dependency或使用即可。
下面介绍详细的入门步骤。
本页采用Kotlin作为开发语言**若你希望使用 Java 开发**, 请参阅: [mirai-japt](https://github.com/mamoe/mirai-japt)
## 起步步骤
通过编写Kotlin程序以第三方库的形式调用```mirai-core```并定义你的Mirai Bot行为。
假如已经对Gradle有一定了解可跳过12
### 1 安装IDEA与JDK
- JDK 要求6以上
### 2 新建Gradle项目
*使用gradle项目可能需要代理在IDEA的```settings```->```proxy settings```中可以设置
- 在```File->new project```中选择```Gradle```
- 在面板中的```Additional Libraries and Frameworks```中勾选```Java```以及```Kotlin/JVM```
- 点击```next```,填入```GroupId```与```ArtifactId```(对于测试项目来说,可随意填写)
- 点击```next```,点击```Use default gradle wrapper(recommended)```
- 创建项目完成
### 3 添加依赖
- 打开项目的```Project```面板,点击编辑```build.gradle```
- 首先添加repositories
```groovy
//添加jcenter仓库
/*
repositories {
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
mavenCentral()
}
原文内容,更新为下文
*/
repositories {
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
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:1.1-EA'//此处版本应替换为当前最新
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.join
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.contact.Member
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(At(sender as Member) + " 给爷爬 ")
}
(contains("舔") or contains("刘老板")) {
reply("刘老板太强了")
}
}
miraiBot.join() // 等待 Bot 离线, 避免主线程退出
}
```
- 单击编辑器内第8行(```suspend fun main```)左侧的run按钮(绿色三角)等待MiraiBot成功登录。
- 本例的功能中在任意群内任意成员发送包含“舔”字或“刘老板”字样的消息MiraiBot会回复“刘老板太强了”
至此简单的入门已经结束下面可根据不同的需求参阅wiki进行功能的添加。
下面,可以尝试对不同事件进行监听[Mirai Guide - Subscribe Events](/docs/guide_subscribe_events.md)
### 此外还可以使用Maven作为包管理工具
本项目推荐使用gradle因此不提供详细入门指导
```xml
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-core-qqandroid</artifactId>
<version>0.23.0</version> <!-- 替换版本为最新版本 -->
</dependency>
</dependencies>
```

View File

@ -1,103 +0,0 @@
# Mirai Guide - Quick Start
由于 mirai 项目在快速推进中,因此内容时有变动,本文档的最后更新日期为```2020/6/22```,对应版本```1.0.2```
本文适用于对 Kotlin 较熟悉的开发者,
使用 mirai 作为第三方依赖库引用到任意一个 Kotlin, Java 或其他 JVM 平台的项目
**若你希望一份更基础的教程**, 请参阅: [mirai-guide-getting-started](guide_getting_started.md)
## 构建需求
- JDK 6 或更高
## 获取 Demo
可在 [mirai-demos](https://github.com/mamoe/mirai-demos) 中获取已经配置好依赖的示例项目.
## Quick Start
请将 `VERSION` 替换为 `mirai-core` 的最新版本号(如 `1.0.4`):
[![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/)
### 添加依赖
可通过以下三种方法之一添加 mirai 依赖.
#### Maven
```xml
<repositories>
<repository>
<id>jcenter</id>
<url>https://jcenter.bintray.com/</url>
</repository>
</repositories>
```
```xml
<dependencies>
<dependency>
<groupId>net.mamoe</groupId>
<artifactId>mirai-core-qqandroid</artifactId>
<version>0.23.0</version> <!-- 替换版本为最新版本 -->
</dependency>
</dependencies>
```
#### Gradle (推荐)
Mirai 只发布在 `jcenter`, 因此请确保添加 `jcenter()` 仓库:
```kotlin
repositories{
jcenter()
}
```
**注意:**
Mirai 核心由 API 模块(`mirai-core`)和协议模块组成。依赖协议模块时会自动依赖相应版本的 API 模块。
请参照下文选择目标平台的依赖添加。
如果你只用 Java / Kotlin 或其他语言开发 JVM 平台应用,只需要添加下文第一条。
如果你只开发 Android 应用,只需添加下文第三条。
**jvm** (JVM 平台源集)
```kotlin
implementation("net.mamoe:mirai-core-qqandroid:VERSION")
```
**common** (Kotlin 多平台项目的通用源集)
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-common:VERSION")
```
**android** (Android 平台源集)
**注意**: 在 [KT-37152](https://youtrack.jetbrains.com/issue/KT-37152) 修复前, mirai 无法支持 Android 平台目标.
```kotlin
implementation("net.mamoe:mirai-core-qqandroid-android:VERSION")
```
#### 直接导入jar包 (不推荐)
下载已经编译好的 Jar 包, 并添加 Jar 依赖:
- [mirai-core](https://github.com/mamoe/mirai-repo/tree/master/shadow/mirai-core)
- [mirai-qqandroid](https://github.com/mamoe/mirai-repo/tree/master/shadow/mirai-core-qqandroid)
### 开始使用
```kotlin
val bot = Bot(qqId, password).alsoLogin()
bot.subscribeAlways<GroupMessageEvent> { event ->
if (event.message.content.contains("你好")) {
reply("你好!")
} else if (event.message.content.contains("你好")) {
File("C:\\image.png").sendAsImage()
}
}
bot.subscribeAlways<MemberPermissionChangedEvent> { event ->
if (event.kind == BECOME_OPERATOR)
reply("${event.member.id} 成为了管理员")
}
```

View File

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

View File

@ -132,3 +132,5 @@ mirai-console-loader 应运而生,它的工作就是简化 console 启动流
以上就是整个 Mirai 生态的概览,如有疏漏或错误,欢迎提出 Issue 修正。 以上就是整个 Mirai 生态的概览,如有疏漏或错误,欢迎提出 Issue 修正。
实体关系图采用 [Mermaid](https://github.com/mermaid-js/mermaid) 绘制。 实体关系图采用 [Mermaid](https://github.com/mermaid-js/mermaid) 绘制。
> [回到 Mirai 文档索引](README.md)

View File

@ -26,7 +26,7 @@ mirai 项目整体由 核心 (`mirai-core`) 与 控制台(`mirai-console`) 组
`mirai-core` 设计为一个 **`支持库`**, 意味着它可以被独立依赖, 在任意项目中使用. 详见下文. `mirai-core` 设计为一个 **`支持库`**, 意味着它可以被独立依赖, 在任意项目中使用. 详见下文.
- `mirai-serialization` 依赖 `mirai-core`, 是 mirai-core 的序列化支持模块. 提供 `Message` 类型的序列化支持与相关 [mirai 码](mirai-code-specification.md) 支持. - `mirai-serialization` 依赖 `mirai-core`, 是 mirai-core 的序列化支持模块. 提供 `Message` 类型的序列化支持与相关 [mirai 码](MiraiCodeSepecification.md) 支持.
此模块自 mirai `1.1.0` 起可用, 引用方法同 `mirai-core`. 此模块自 mirai `1.1.0` 起可用, 引用方法同 `mirai-core`.
- [`mirai-console`](https://github.com/mamoe/mirai-console) 是基于 `mirai-core` 的, 支持插件加载, 指令系统, 和配置等的**控制台框架**. - [`mirai-console`](https://github.com/mamoe/mirai-console) 是基于 `mirai-core` 的, 支持插件加载, 指令系统, 和配置等的**控制台框架**.

View File

@ -0,0 +1,74 @@
```mermaid
classDiagram
class Bot {
+friends: ContactList
+groups: ContactList
+getFriend(Long) Friend?
+getFriendOrNull(Long) Friend
+getGroup(Long) Group?
+getGroupOrFail(Long) Group
+login()
+close()
}
class ContactOrBot {
+id: Int
}
class UserOrBot {
+nudge() Nudge
}
class Contact {
+bot: Bot
+sendMessage(Message) MessageReceipt
+sendMessage(String) MessageReceipt
+uploadImage(ExternalImage) Image
}
class User {
+nick: String
+remark: String
+avatarUrl: String
}
class Group {
+members: ContactList
+name: String
+settings: GroupSettings
+owner: NormalMember
+botMuteRemaining: Long
+botPermission: MemberPermission
+quit() Boolean
+uploadVoice() Voice
}
class NormalMember {
+mute()
+kick()
}
class AnonymousMember {
+anonymousId: String
}
class Member {
+group: Group
}
ContactOrBot<|--Contact
ContactOrBot<|--UserOrBot
UserOrBot<|--Bot
UserOrBot<|--User
Contact<|--User
Contact<|--Group
User<|--Member
User<|--Friend
Member<|--NormalMember
Member<|--AnonymousMember
```

View File

@ -0,0 +1,47 @@
```mermaid
classDiagram
class MessageChain
MessageChain : List~SingleMessage~
Message<|--MessageChain
Message<|--SingleMessage
MessageChain o-- SingleMessage
SingleMessage<|--MessageContent
SingleMessage<|--MessageMetadata
%%%
MessageMetadata<|--QuoteReply
MessageMetadata<|--MessageSource
%%
MessageSource<|--OnlineMessageSource
MessageSource<|--OfflineMessageSource
MessageContent<|--PlainText
MessageContent<|--Image
MessageContent<|--At
MessageContent<|--AtAll
MessageContent<|--Face
MessageContent<|--ForwardMessage
MessageContent<|--HummerMessage
HummerMessage<|--PokeMessage
HummerMessage<|--VipFace
HummerMessage<|--FlashImage
MessageContent<|--RichMessage
RichMessage<|--ServiceMessage
RichMessage<|--LightApp
MessageContent<|--PttMessage
PttMessage<|--Voice
```

View File

@ -24,6 +24,7 @@ description = "Mirai core shadowed"
dependencies { dependencies {
api(project(":mirai-core")) api(project(":mirai-core"))
api(project(":mirai-core-api")) api(project(":mirai-core-api"))
api(project(":mirai-core-utils"))
} }
configurePublishing("mirai-core-all") configurePublishing("mirai-core-all")

View File

@ -24,8 +24,7 @@ import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLogger
import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.PlannedRemoval
import java.util.* import java.util.concurrent.ConcurrentHashMap
import kotlin.NoSuchElementException
/** /**
* 登录, 返回 [this] * 登录, 返回 [this]
@ -153,14 +152,14 @@ public interface Bot : CoroutineScope, ContactOrBot, UserOrBot {
public companion object { public companion object {
@Suppress("ObjectPropertyName") @Suppress("ObjectPropertyName")
internal val _instances: WeakHashMap<Long, Bot> = WeakHashMap() internal val _instances: ConcurrentHashMap<Long, Bot> = ConcurrentHashMap()
/** /**
* 复制一份此时的 [Bot] 实例列表. * 复制一份此时的 [Bot] 实例列表.
*/ */
@JvmStatic @JvmStatic
public val instances: List<Bot> public val instances: List<Bot>
get() = _instances.values.filterNotNull() get() = _instances.values.toList()
/** /**
* 复制一份此时的 [Bot] 实例列表. * 复制一份此时的 [Bot] 实例列表.

View File

@ -0,0 +1,32 @@
/*
* Copyright 2019-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.message.data.Message
import net.mamoe.mirai.utils.MemberDeprecatedApi
/**
* 匿名
*
* 代表匿名群成员
*/
public interface AnonymousMember : Member {
/** 该匿名群成员 ID */
public val anonymousId: String
@MemberDeprecatedApi(message = "无法发送信息至 AnonymousMember")
@Deprecated(level = DeprecationLevel.ERROR, message = "无法发送信息至 AnonymousMember")
public override suspend fun sendMessage(message: Message): Nothing
@Deprecated(level = DeprecationLevel.ERROR, message = "无法发送信息至 AnonymousMember")
@MemberDeprecatedApi(message = "无法发送信息至 AnonymousMember")
public override suspend fun sendMessage(message: String): Nothing
}

View File

@ -53,13 +53,13 @@ public interface Group : Contact, CoroutineScope {
* *
* @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员
*/ */
public val owner: Member public val owner: NormalMember
/** /**
* [Bot] 在群内的 [Member] 实例 * [Bot] 在群内的 [Member] 实例
*/ */
@MiraiExperimentalApi @MiraiExperimentalApi
public val botAsMember: Member public val botAsMember: NormalMember
/** /**
* 机器人被禁言还剩余多少秒 * 机器人被禁言还剩余多少秒
@ -89,14 +89,14 @@ public interface Group : Contact, CoroutineScope {
* *
* [Group] 实例创建的时候查询一次. 并与事件同步事件更新. * [Group] 实例创建的时候查询一次. 并与事件同步事件更新.
*/ */
public val members: ContactList<Member> public val members: ContactList<NormalMember>
/** /**
* 获取群成员实例. 不存在时返回 `null`. * 获取群成员实例. 不存在时返回 `null`.
* *
* [id] [Bot.id] 时返回 [botAsMember]. * [id] [Bot.id] 时返回 [botAsMember].
*/ */
public operator fun get(id: Long): Member? public operator fun get(id: Long): NormalMember?
@Deprecated("Use get", ReplaceWith("get(id)")) @Deprecated("Use get", ReplaceWith("get(id)"))
@PlannedRemoval("2.0-M2") @PlannedRemoval("2.0-M2")
@ -104,14 +104,14 @@ public interface Group : Contact, CoroutineScope {
* 获取群成员实例, 不存在则 null * 获取群成员实例, 不存在则 null
* [id] [Bot.id] 时返回 [botAsMember] * [id] [Bot.id] 时返回 [botAsMember]
*/ */
public fun getOrNull(id: Long): Member? = get(id) public fun getOrNull(id: Long): NormalMember? = get(id)
/** /**
* 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException]. * 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException].
* *
* [id] [Bot.id] 时返回 [botAsMember]. * [id] [Bot.id] 时返回 [botAsMember].
*/ */
public fun getOrFail(id: Long): Member = public fun getOrFail(id: Long): NormalMember =
get(id) ?: throw NoSuchElementException("member $id not found in group ${this.id}") get(id) ?: throw NoSuchElementException("member $id not found in group ${this.id}")
@ -125,7 +125,7 @@ public interface Group : Contact, CoroutineScope {
/** /**
* [member] 是本群成员时返回 `true`. 将同时成员 [所属群][Member.group]. 同一个用户在不同群内的 [Member] 对象不相等. * [member] 是本群成员时返回 `true`. 将同时成员 [所属群][Member.group]. 同一个用户在不同群内的 [Member] 对象不相等.
*/ */
public operator fun contains(member: Member): Boolean = member in members public operator fun contains(member: NormalMember): Boolean = member in members
/** /**

View File

@ -12,33 +12,29 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.BotMuteEvent
import net.mamoe.mirai.JavaFriendlyAPI import net.mamoe.mirai.event.events.MemberMuteEvent
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.MemberPermissionChangeEvent
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.MessageReceipt.Companion.recall
import net.mamoe.mirai.message.action.MemberNudge import net.mamoe.mirai.message.action.MemberNudge
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.utils.MemberDeprecatedApi
import net.mamoe.mirai.message.data.toPlainText
import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.WeakRefProperty import net.mamoe.mirai.utils.WeakRefProperty
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
/** /**
* 代表一位群成员. * 代表一位群成员.
* *
* 群成员可能也是好友, 但他们在对象类型上不同. * 群成员分为 [普通成员][NormalMember] [匿名成员][AnonymousMember]
* 群成员可以通过 [asFriend] 得到相关好友对象.
* *
* ## 与好友相关的操作 * 一个群成员可能也是机器人的好友, 但他们在对象类型上不同 ([Member] != [Friend]). 可以通过 [Member.asFriend] 得到相关好友对象.
*
* ## 相关的操作
* [Member.isFriend] 判断此成员是否为好友 * [Member.isFriend] 判断此成员是否为好友
* [Member.isAnonymous] 判断此成员是否为匿名群成员
* [Member.isNormal] 判断此成员是否为正常群成员
*/ */
@Suppress("INAPPLICABLE_JVM_NAME", "EXPOSED_SUPER_CLASS")
@OptIn(JavaFriendlyAPI::class)
public interface Member : User { public interface Member : User {
/** /**
* 所在的群. * 所在的群.
@ -56,125 +52,62 @@ public interface Member : User {
/** /**
* 群名片. 可能为空. * 群名片. 可能为空.
* *
* 管理员和群主都可修改任何人包括群主的群名片. * @see [NormalMember.nameCard]
* * @see [AnonymousMember.nameCard]
* 在修改时将会异步上传至服务器.
*
* @see [nameCardOrNick] 获取非空群名片或昵称
*
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
*/ */
public var nameCard: String public val nameCard: String
/** /**
* 群头衔. * 群头衔.
* *
* 仅群主可以修改群头衔. * [AnonymousMember] 时一定是 `"匿名"`
* *
* 在修改时将会异步上传至服务器. * @see [NormalMember.specialTitle]
*
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
*/ */
public var specialTitle: String public val specialTitle: String
/** @MemberDeprecatedApi("仅 NormalMember 支持 muteTimeRemaining. 请先检查类型为 NormalMember.")
* 被禁言剩余时长. 单位为秒. @PlannedRemoval("2.0-M2")
*
* @see isMuted 判断改成员是否处于禁言状态
* @see mute 设置禁言
* @see unmute 取消禁言
*/
public val muteTimeRemaining: Int public val muteTimeRemaining: Int
/** /**
* 禁言. * 禁言这个群成员 [durationSeconds] , 在机器人无权限操作时抛出 [PermissionDeniedException].
* *
* QQ 中最小操作和显示的时间都是一分钟. * QQ 中最小操作和显示的时间都是一分钟. 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
* *
* 管理员可禁言成员, 群主可禁言管理员和群员. * 管理员可禁言成员, 群主可禁言管理员和群员.
* *
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常. * @param durationSeconds 持续时间. 精确到秒. 最短 0 , 最长 30 . 超过范围则会抛出异常 [IllegalStateException].
* @return 机器人无权限时返回 `false`
* *
* @see Member.isMuted 判断此成员是否正处于禁言状态中 * @see NormalMember.isMuted 判断此成员是否正处于禁言状态中
* @see unmute 取消禁言此成员 * @see NormalMember.unmute 取消禁言此成员
*
* @see Int.minutesToSeconds
* @see Int.hoursToSeconds
* @see Int.daysToSeconds
* *
* @see MemberMuteEvent 成员被禁言事件 * @see MemberMuteEvent 成员被禁言事件
* @see BotMuteEvent Bot 被禁言事件
* *
* @throws PermissionDeniedException 无权限修改时抛出 * @see Member.mute 支持 Kotlin [kotlin.time.Duration] 的扩展
*/ */
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun mute(durationSeconds: Int) public suspend fun mute(durationSeconds: Int)
/** @MemberDeprecatedApi("仅 NormalMember 支持 unmute. 请先检查类型为 NormalMember.")
* 解除禁言. @PlannedRemoval("2.0-M2")
*
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
*
* @see Member.isMuted 判断此成员是否正处于禁言状态中
*
* @see MemberUnmuteEvent 成员被取消禁言事件
*
* @throws PermissionDeniedException 无权限修改时抛出
*/
@JvmBlockingBridge
public suspend fun unmute() public suspend fun unmute()
/** @MemberDeprecatedApi("仅 NormalMember 支持 kick. 请先检查类型为 NormalMember.")
* 踢出该成员. @PlannedRemoval("2.0-M2")
*
* 管理员可踢出成员, 群主可踢出管理员和群员.
*
* @see MemberLeaveEvent.Kick 成员被踢出事件.
* @throws PermissionDeniedException 无权限修改时
*/
@JvmBlockingBridge
public suspend fun kick(message: String = "") public suspend fun kick(message: String = "")
/** @MemberDeprecatedApi("仅 NormalMember 支持 sendMessage. 请先检查类型为 NormalMember.")
* 向群成员发送消息.
* 若群成员同时是好友, 则会发送好友消息. 否则发送临时会话消息.
*
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessagePreSendEvent 当此成员是好友时发送消息前事件
* @see FriendMessagePostSendEvent 当此成员是好友时发送消息后事件
*
* @see TempMessagePreSendEvent 当此成员不是好友时发送消息前事件
* @see TempMessagePostSendEvent 当此成员不是好友时发送消息后事件
*
* @throws EventCancelledException 当发送消息事件被取消时抛出
* @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出
* @throws MessageTooLargeException 当消息过长时抛出
* @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty])
*
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Member> public override suspend fun sendMessage(message: Message): MessageReceipt<Member>
/** @MemberDeprecatedApi("仅 NormalMember 支持 sendMessage. 请先检查类型为 NormalMember.")
* 发送纯文本消息 public override suspend fun sendMessage(message: String): MessageReceipt<Member>
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Member> =
this.sendMessage(message.toPlainText())
/** @MemberDeprecatedApi("仅 NormalMember 支持 nudge. 请先检查类型为 NormalMember.")
* 创建一个 "戳一戳" 消息 @PlannedRemoval("2.0-M2")
*
* @see Nudge.sendTo 发送这个戳一戳消息
*/
@MiraiExperimentalApi @MiraiExperimentalApi
public override fun nudge(): MemberNudge = MemberNudge(this) public override fun nudge(): MemberNudge
} }
/** /**
@ -200,7 +133,8 @@ public inline val Member.isFriend: Boolean
*/ */
@Deprecated( @Deprecated(
"Ambiguous function name and its behaviour. Use asFriendOrNull and let manually.", "Ambiguous function name and its behaviour. Use asFriendOrNull and let manually.",
ReplaceWith("this.asFriendOrNull()?.let(block)") ReplaceWith("this.asFriendOrNull()?.let(block)"),
level = DeprecationLevel.ERROR
) )
@PlannedRemoval("2.0-M2") @PlannedRemoval("2.0-M2")
public inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? { public inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? {
@ -213,39 +147,5 @@ public inline fun <R> Member.takeIfIsFriend(block: (Friend) -> R): R? {
* [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [User.nick] * [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [User.nick]
*/ */
public val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick public val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick
public val Member.isNormal: Boolean get() = this is NormalMember
/** public val Member.isAnonymous: Boolean get() = this is AnonymousMember
* 获取非空群名片或昵称.
*
* @return [User] [Member] 时返回 [Member.nameCardOrNick]
*
* 否则返回 [Member.nick]
*/
public val User.nameCardOrNick: String
get() = when (this) {
is Member -> this.nameCardOrNick
else -> this.nick
}
/**
* 判断群成员是否处于禁言状态.
*/
public val Member.isMuted: Boolean
get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt()
/**
* @see Member.mute
*/
@ExperimentalTime
public suspend inline fun Member.mute(duration: Duration) {
require(duration.inDays <= 30) { "duration must be at most 1 month" }
require(duration.inSeconds > 0) { "duration must be greater than 0 second" }
this.mute(duration.inSeconds.toInt())
}
/**
* @see Member.mute
*/
@Deprecated("Convert duration to int manually.", ReplaceWith("this.mute(durationSeconds.toInt())"))
@PlannedRemoval("2.0-M2")
public suspend inline fun Member.mute(durationSeconds: Long): Unit = this.mute(durationSeconds.toInt())

View File

@ -0,0 +1,173 @@
/*
* Copyright 2019-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.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.MessageReceipt.Companion.recall
import net.mamoe.mirai.message.action.MemberNudge
import net.mamoe.mirai.message.action.Nudge
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.isContentEmpty
import net.mamoe.mirai.message.data.toPlainText
import net.mamoe.mirai.utils.MemberDeprecatedApi
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.PlannedRemoval
import kotlin.time.Duration
import kotlin.time.ExperimentalTime
/**
* 代表一位正常的群成员.
*
* 群成员可能也是好友, 但他们在对象类型上不同.
* 群成员可以通过 [asFriend] 得到相关好友对象.
*
* ## 相关的操作
* [Member.isFriend] 判断此成员是否为好友
*/
@OptIn(MemberDeprecatedApi::class)
public interface NormalMember : Member {
/**
* 群名片. 可能为空.
*
* 管理员和群主都可修改任何人包括群主的群名片.
*
* 在修改时将会异步上传至服务器.
*
* @see [nameCardOrNick] 获取非空群名片或昵称
*
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
*/
public override var nameCard: String
/**
* 群头衔.
*
* 仅群主可以修改群头衔.
*
* 在修改时将会异步上传至服务器.
*
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时
*/
public override var specialTitle: String
/**
* 被禁言剩余时长. 单位为秒.
*
* @see isMuted 判断改成员是否处于禁言状态
* @see mute 设置禁言
* @see unmute 取消禁言
*/
public override val muteTimeRemaining: Int
/**
* 解除禁言.
*
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
*
* @see Member.isMuted 判断此成员是否正处于禁言状态中
*
* @see MemberUnmuteEvent 成员被取消禁言事件
*
* @throws PermissionDeniedException 无权限修改时抛出
*/
@JvmBlockingBridge
public override suspend fun unmute()
/**
* 踢出该成员.
*
* 管理员可踢出成员, 群主可踢出管理员和群员.
*
* @see MemberLeaveEvent.Kick 成员被踢出事件.
* @throws PermissionDeniedException 无权限修改时
*/
@JvmBlockingBridge
public override suspend fun kick(message: String)
/**
* 向群成员发送消息.
* 若群成员同时是好友, 则会发送好友消息. 否则发送临时会话消息.
*
* 单条消息最大可发送 4500 字符或 50 张图片.
*
* @see FriendMessagePreSendEvent 当此成员是好友时发送消息前事件
* @see FriendMessagePostSendEvent 当此成员是好友时发送消息后事件
*
* @see TempMessagePreSendEvent 当此成员不是好友时发送消息前事件
* @see TempMessagePostSendEvent 当此成员不是好友时发送消息后事件
*
* @throws EventCancelledException 当发送消息事件被取消时抛出
* @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出
* @throws MessageTooLargeException 当消息过长时抛出
* @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty])
*
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: Message): MessageReceipt<Member>
/**
* 发送纯文本消息
* @see sendMessage
*/
@JvmBlockingBridge
public override suspend fun sendMessage(message: String): MessageReceipt<Member> =
this.sendMessage(message.toPlainText())
/**
* 创建一个 "戳一戳" 消息
*
* @see Nudge.sendTo 发送这个戳一戳消息
*/
@MiraiExperimentalApi
public override fun nudge(): MemberNudge = MemberNudge(this)
}
/**
* 获取非空群名片或昵称.
*
* @return [User] [Member] 时返回 [Member.nameCardOrNick]
*
* 否则返回 [Member.nick]
*/
public val User.nameCardOrNick: String
get() = when (this) {
is NormalMember -> this.nameCardOrNick
else -> this.nick
}
/**
* 判断群成员是否处于禁言状态.
*/
public val NormalMember.isMuted: Boolean
get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt()
/**
* @see Member.mute
*/
@ExperimentalTime
public suspend inline fun NormalMember.mute(duration: Duration) {
require(duration.inDays <= 30) { "duration must be at most 1 month" }
require(duration.inSeconds > 0) { "duration must be greater than 0 second" }
this.mute(duration.inSeconds.toInt())
}
/**
* @see Member.mute
*/
@Deprecated("Convert duration to int manually.", ReplaceWith("this.mute(durationSeconds.toInt())"))
@PlannedRemoval("2.0-M2")
public suspend inline fun NormalMember.mute(durationSeconds: Long): Unit = this.mute(durationSeconds.toInt())

View File

@ -14,6 +14,7 @@
package net.mamoe.mirai.event package net.mamoe.mirai.event
import kotlinx.coroutines.* import kotlinx.coroutines.*
import net.mamoe.mirai.utils.EventListenerLikeJava
import java.lang.reflect.Method import java.lang.reflect.Method
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@ -238,6 +239,23 @@ public fun CoroutineScope.registerEvents(
} }
} }
private fun Method.isKotlinFunction(): Boolean {
if (getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false
if (declaringClass.getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false
@Suppress("RemoveRedundantQualifierName") // for strict
return declaringClass.getDeclaredAnnotation(kotlin.Metadata::class.java) != null
}
private fun Method.invokeWithErrorReport(self: Any?, vararg args: Any?): Any? = try {
invoke(self, *args)
} catch (exception: IllegalArgumentException) {
throw IllegalArgumentException(
"Internal Error: $exception, method=${this}, this=$self, arguments=$args, please report to https://github.com/mamoe/mirai",
exception
)
}
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
private fun Method.registerEvent( private fun Method.registerEvent(
@ -248,7 +266,7 @@ private fun Method.registerEvent(
): Listener<Event> { ): Listener<Event> {
this.isAccessible = true this.isAccessible = true
val kotlinFunction = kotlin.runCatching { this.kotlinFunction }.getOrNull() val kotlinFunction = kotlin.runCatching { this.kotlinFunction }.getOrNull()
return if (kotlinFunction != null) { return if (kotlinFunction != null && isKotlinFunction()) {
// kotlin functions // kotlin functions
val param = kotlinFunction.parameters val param = kotlinFunction.parameters
@ -337,7 +355,7 @@ private fun Method.registerEvent(
val paramType = this.parameters[0].type val paramType = this.parameters[0].type
check(this.parameterCount == 1 && Event::class.java.isAssignableFrom(paramType)) { check(this.parameterCount == 1 && Event::class.java.isAssignableFrom(paramType)) {
"Illegal method parameter. Required one exact Event subclass. found $paramType" "Illegal method parameter. Required one exact Event subclass. found ${this.parameters.contentToString()}"
} }
when (this.returnType) { when (this.returnType) {
Void::class.java, Void.TYPE, Nothing::class.java -> { Void::class.java, Void.TYPE, Nothing::class.java -> {
@ -350,11 +368,11 @@ private fun Method.registerEvent(
if (annotation.ignoreCancelled) { if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) { if ((this as? CancellableEvent)?.isCancelled != true) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this) this@registerEvent.invokeWithErrorReport(owner, this@subscribeAlways)
} }
} }
} else withContext(Dispatchers.IO) { } else withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this) this@registerEvent.invokeWithErrorReport(owner, this@subscribeAlways)
} }
} }
} }
@ -368,11 +386,11 @@ private fun Method.registerEvent(
if (annotation.ignoreCancelled) { if (annotation.ignoreCancelled) {
if ((this as? CancellableEvent)?.isCancelled != true) { if ((this as? CancellableEvent)?.isCancelled != true) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this) as ListeningStatus this@registerEvent.invokeWithErrorReport(owner, this@subscribe) as ListeningStatus
} }
} else ListeningStatus.LISTENING } else ListeningStatus.LISTENING
} else withContext(Dispatchers.IO) { } else withContext(Dispatchers.IO) {
this@registerEvent.invoke(owner, this) as ListeningStatus this@registerEvent.invokeWithErrorReport(owner, this@subscribe) as ListeningStatus
} }
} }

View File

@ -25,9 +25,10 @@
- (`1.3.0+`) Bot 被戳: BotNudgedEvent - (`1.3.0+`) Bot 被戳: BotNudgedEvent
### [消息](message.kt) ### [消息](message.kt)
- (`1.1.0-`) 主动发送消息: MessageSendEvent - 被动收到消息MessageEvent
- 群消息: GroupMessageSendEvent - 群消息GroupMessageEvent
- 好友消息: FriendMessageSendEvent - 好友消息FriendMessageEvent
- 群临时会话消息TempMessageEvent
- (`1.1.0+`) 主动发送消息前: MessagePreSendEvent - (`1.1.0+`) 主动发送消息前: MessagePreSendEvent
- 群消息: GroupMessagePreSendEvent - 群消息: GroupMessagePreSendEvent
- 好友消息: FriendMessagePreSendEvent - 好友消息: FriendMessagePreSendEvent
@ -44,6 +45,9 @@
- 图片上传完成: ImageUploadEvent - 图片上传完成: ImageUploadEvent
- 图片上传成功: Succeed - 图片上传成功: Succeed
- 图片上传失败: Failed - 图片上传失败: Failed
- (`1.1.0-`) ~~主动发送消息: MessageSendEvent~~
- ~~群消息: GroupMessageSendEvent~~
- ~~好友消息: FriendMessageSendEvent~~
### [](group.kt) ### [](group.kt)
- 机器人被踢出群或在其他客户端主动退出一个群: BotLeaveEvent - 机器人被踢出群或在其他客户端主动退出一个群: BotLeaveEvent
@ -60,7 +64,7 @@
- 入群公告改变: GroupEntranceAnnouncementChangeEvent - 入群公告改变: GroupEntranceAnnouncementChangeEvent
- 全员禁言状态改变: GroupMuteAllEvent - 全员禁言状态改变: GroupMuteAllEvent
- 匿名聊天状态改变: GroupAllowAnonymousChatEvent - 匿名聊天状态改变: GroupAllowAnonymousChatEvent
- 坦白说状态改变: GroupAllowConfessTalkEvent - (`1.3.0-`) ~~坦白说状态改变: GroupAllowConfessTalkEvent~~
- 允许群员邀请好友加群状态改变: GroupAllowMemberInviteEvent - 允许群员邀请好友加群状态改变: GroupAllowMemberInviteEvent
#### 群成员 #### 群成员

View File

@ -16,10 +16,7 @@ package net.mamoe.mirai.event.events
import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.Mirai import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.*
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.BroadcastControllable
import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.Packet
@ -245,14 +242,14 @@ public data class GroupAllowMemberInviteEvent internal constructor(
* 成员已经加入群的事件 * 成员已经加入群的事件
*/ */
public sealed class MemberJoinEvent( public sealed class MemberJoinEvent(
public override val member: Member public override val member: NormalMember
) : GroupMemberEvent, BotPassiveEvent, Packet, ) : GroupMemberEvent, BotPassiveEvent, Packet,
AbstractEvent() { AbstractEvent() {
/** /**
* 被邀请加入群 * 被邀请加入群
*/ */
public data class Invite internal constructor( public data class Invite internal constructor(
public override val member: Member public override val member: NormalMember
) : MemberJoinEvent(member) { ) : MemberJoinEvent(member) {
public override fun toString(): String = "MemberJoinEvent.Invite(member=${member.id})" public override fun toString(): String = "MemberJoinEvent.Invite(member=${member.id})"
} }
@ -261,7 +258,7 @@ public sealed class MemberJoinEvent(
* 成员主动加入群 * 成员主动加入群
*/ */
public data class Active internal constructor( public data class Active internal constructor(
public override val member: Member public override val member: NormalMember
) : MemberJoinEvent(member) { ) : MemberJoinEvent(member) {
public override fun toString(): String = "MemberJoinEvent.Active(member=${member.id})" public override fun toString(): String = "MemberJoinEvent.Active(member=${member.id})"
} }
@ -271,7 +268,7 @@ public sealed class MemberJoinEvent(
* 此时 [member] [Member.permission] 肯定是 [MemberPermission.OWNER] * 此时 [member] [Member.permission] 肯定是 [MemberPermission.OWNER]
*/ */
public data class Retrieve internal constructor( public data class Retrieve internal constructor(
public override val member: Member public override val member: NormalMember
) : MemberJoinEvent(member) { ) : MemberJoinEvent(member) {
override fun toString(): String = "MemberJoinEvent.Retrieve(member=${member.id})" override fun toString(): String = "MemberJoinEvent.Retrieve(member=${member.id})"
} }
@ -285,11 +282,11 @@ public sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() {
* 成员被踢出群. 成员不可能是机器人自己. * 成员被踢出群. 成员不可能是机器人自己.
*/ */
public data class Kick( public data class Kick(
public override val member: Member, public override val member: NormalMember,
/** /**
* 操作人. null 则是机器人操作. * 操作人. null 则是机器人操作.
*/ */
public override val operator: Member? public override val operator: NormalMember?
) : MemberLeaveEvent(), Packet, GroupOperableEvent { ) : MemberLeaveEvent(), Packet, GroupOperableEvent {
public override fun toString(): String = "MemberLeaveEvent.Kick(member=${member.id}, operator=${operator?.id})" public override fun toString(): String = "MemberLeaveEvent.Kick(member=${member.id}, operator=${operator?.id})"
} }
@ -298,7 +295,7 @@ public sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent() {
* 成员主动离开 * 成员主动离开
*/ */
public data class Quit( public data class Quit(
public override val member: Member public override val member: NormalMember
) : MemberLeaveEvent(), Packet { ) : MemberLeaveEvent(), Packet {
public override fun toString(): String = "MemberLeaveEvent.Quit(member=${member.id})" public override fun toString(): String = "MemberLeaveEvent.Quit(member=${member.id})"
} }
@ -439,7 +436,7 @@ public data class MemberSpecialTitleChangeEvent internal constructor(
* 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改. * 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改.
* null 时则是机器人操作. * null 时则是机器人操作.
*/ */
public override val operator: Member? public override val operator: NormalMember?
) : GroupMemberEvent, GroupOperableEvent, AbstractEvent() ) : GroupMemberEvent, GroupOperableEvent, AbstractEvent()
// endregion // endregion

View File

@ -440,7 +440,7 @@ public class FriendMessageEvent constructor(
public override val sender: Friend, public override val sender: Friend,
public override val message: MessageChain, public override val message: MessageChain,
public override val time: Int public override val time: Int
) : AbstractEvent(), MessageEvent, MessageEventExtensions<User, Contact>, BroadcastControllable, FriendEvent { ) : AbstractMessageEvent(), MessageEvent, MessageEventExtensions<User, Contact>, BroadcastControllable, FriendEvent {
init { init {
val source = val source =
message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message")
@ -480,7 +480,7 @@ public class GroupMessageEvent(
public override val sender: Member, public override val sender: Member,
public override val message: MessageChain, public override val message: MessageChain,
public override val time: Int public override val time: Int
) : AbstractEvent(), GroupAwareMessageEvent, MessageEvent, Event, GroupEvent { ) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageEvent, Event, GroupEvent {
init { init {
val source = message[MessageSource] ?: error("Cannot find MessageSource from message") val source = message[MessageSource] ?: error("Cannot find MessageSource from message")
check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessage must be an instance of OnlineMessageSource.Incoming.FromGroup" } check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessage must be an instance of OnlineMessageSource.Incoming.FromGroup" }
@ -512,7 +512,7 @@ public class TempMessageEvent(
public override val sender: Member, public override val sender: Member,
public override val message: MessageChain, public override val message: MessageChain,
public override val time: Int public override val time: Int
) : AbstractEvent(), GroupAwareMessageEvent, MessageEvent, BroadcastControllable { ) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageEvent, BroadcastControllable {
init { init {
val source = message[MessageSource] ?: error("Cannot find MessageSource from message") val source = message[MessageSource] ?: error("Cannot find MessageSource from message")
check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a TempMessage must be an instance of OnlineMessageSource.Incoming.FromTemp" } check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a TempMessage must be an instance of OnlineMessageSource.Incoming.FromTemp" }
@ -538,6 +538,75 @@ public interface UserMessageEvent : MessageEvent {
public override val subject: User public override val subject: User
} }
@MiraiInternalApi
public abstract class AbstractMessageEvent : MessageEvent, AbstractEvent() {
public override suspend fun reply(message: Message): MessageReceipt<Contact> =
subject.sendMessage(message.asMessageChain())
public override suspend fun reply(plain: String): MessageReceipt<Contact> =
subject.sendMessage(PlainText(plain).asMessageChain())
public override suspend fun ExternalImage.upload(): Image = this.upload(subject)
public override suspend fun ExternalImage.send(): MessageReceipt<Contact> = this.sendTo(subject)
public override suspend fun Image.send(): MessageReceipt<Contact> = this.sendTo(subject)
public override suspend fun Message.send(): MessageReceipt<Contact> = this.sendTo(subject)
public override suspend fun String.send(): MessageReceipt<Contact> = PlainText(this).sendTo(subject)
// region 引用回复
/**
* 给这个消息事件的主体发送引用回复消息
* 对于好友消息事件, 这个方法将会给好友 ([subject]) 发送消息
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/
public override suspend fun quoteReply(message: MessageChain): MessageReceipt<Contact> =
reply(this.message.quote() + message)
public override suspend fun quoteReply(message: Message): MessageReceipt<Contact> =
reply(this.message.quote() + message)
public override suspend fun quoteReply(plain: String): MessageReceipt<Contact> = reply(this.message.quote() + plain)
public override fun At.isBot(): Boolean = target == bot.id
/**
* 获取图片下载链接
* @return "http://gchat.qpic.cn/gchatpic_new/..."
*/
public override suspend fun Image.url(): String = this@url.queryUrl()
// region 上传图片
public override suspend fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image)
public override suspend fun uploadImage(image: InputStream): Image = subject.uploadImage(image)
public override suspend fun uploadImage(image: File): Image = subject.uploadImage(image)
// endregion
// region 发送图片
public override suspend fun sendImage(image: BufferedImage): MessageReceipt<Contact> = subject.sendImage(image)
public override suspend fun sendImage(image: InputStream): MessageReceipt<Contact> = subject.sendImage(image)
public override suspend fun sendImage(image: File): MessageReceipt<Contact> = subject.sendImage(image)
// endregion
// region 上传图片 (扩展)
public override suspend fun BufferedImage.upload(): Image = upload(subject)
public override suspend fun InputStream.uploadAsImage(): Image = uploadAsImage(subject)
public override suspend fun File.uploadAsImage(): Image = uploadAsImage(subject)
// endregion 上传图片 (扩展)
// region 发送图片 (扩展)
public override suspend fun BufferedImage.send(): MessageReceipt<Contact> = sendTo(subject)
public override suspend fun InputStream.sendAsImage(): MessageReceipt<Contact> = sendAsImageTo(subject)
public override suspend fun File.sendAsImage(): MessageReceipt<Contact> = sendAsImageTo(subject)
// endregion 发送图片 (扩展)
}
/** /**
* 一个 (收到的) 消息事件. * 一个 (收到的) 消息事件.
* *
@ -611,29 +680,27 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/ */
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun reply(message: Message): MessageReceipt<TSubject> = public suspend fun reply(message: Message): MessageReceipt<TSubject>
subject.sendMessage(message.asMessageChain()) as MessageReceipt<TSubject>
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun reply(plain: String): MessageReceipt<TSubject> = public suspend fun reply(plain: String): MessageReceipt<TSubject>
subject.sendMessage(PlainText(plain).asMessageChain()) as MessageReceipt<TSubject>
// endregion // endregion
@JvmSynthetic @JvmSynthetic
public suspend fun ExternalImage.upload(): Image = this.upload(subject) public suspend fun ExternalImage.upload(): Image
@JvmSynthetic @JvmSynthetic
public suspend fun ExternalImage.send(): MessageReceipt<TSubject> = this.sendTo(subject) public suspend fun ExternalImage.send(): MessageReceipt<TSubject>
@JvmSynthetic @JvmSynthetic
public suspend fun Image.send(): MessageReceipt<TSubject> = this.sendTo(subject) public suspend fun Image.send(): MessageReceipt<TSubject>
@JvmSynthetic @JvmSynthetic
public suspend fun Message.send(): MessageReceipt<TSubject> = this.sendTo(subject) public suspend fun Message.send(): MessageReceipt<TSubject>
@JvmSynthetic @JvmSynthetic
public suspend fun String.send(): MessageReceipt<TSubject> = PlainText(this).sendTo(subject) public suspend fun String.send(): MessageReceipt<TSubject>
// region 引用回复 // region 引用回复
/** /**
@ -642,18 +709,16 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta
* 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息 * 对于群消息事件, 这个方法将会给群 ([subject]) 发送消息
*/ */
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun quoteReply(message: MessageChain): MessageReceipt<TSubject> = public suspend fun quoteReply(message: MessageChain): MessageReceipt<TSubject>
reply(this.message.quote() + message)
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun quoteReply(message: Message): MessageReceipt<TSubject> = public suspend fun quoteReply(message: Message): MessageReceipt<TSubject>
reply(this.message.quote() + message)
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun quoteReply(plain: String): MessageReceipt<TSubject> = reply(this.message.quote() + plain) public suspend fun quoteReply(plain: String): MessageReceipt<TSubject>
@JvmSynthetic @JvmSynthetic
public fun At.isBot(): Boolean = target == bot.id public fun At.isBot(): Boolean
/** /**
@ -661,7 +726,7 @@ public interface MessageEventExtensions<out TSender : User, out TSubject : Conta
* @return "http://gchat.qpic.cn/gchatpic_new/..." * @return "http://gchat.qpic.cn/gchatpic_new/..."
*/ */
@JvmSynthetic @JvmSynthetic
public suspend fun Image.url(): String = this@url.queryUrl() public suspend fun Image.url(): String
} }
/** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */ /** 一个消息事件在各平台的相关扩展. 请使用 [MessageEventExtensions] */
@ -679,46 +744,46 @@ public interface MessageEventPlatformExtensions<out TSender : User, out TSubject
// region 上传图片 // region 上传图片
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun uploadImage(image: BufferedImage): Image = subject.uploadImage(image) public suspend fun uploadImage(image: BufferedImage): Image
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun uploadImage(image: InputStream): Image = subject.uploadImage(image) public suspend fun uploadImage(image: InputStream): Image
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun uploadImage(image: File): Image = subject.uploadImage(image) public suspend fun uploadImage(image: File): Image
// endregion // endregion
// region 发送图片 // region 发送图片
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun sendImage(image: BufferedImage): MessageReceipt<TSubject> = subject.sendImage(image) public suspend fun sendImage(image: BufferedImage): MessageReceipt<TSubject>
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun sendImage(image: InputStream): MessageReceipt<TSubject> = subject.sendImage(image) public suspend fun sendImage(image: InputStream): MessageReceipt<TSubject>
@JvmBlockingBridge @JvmBlockingBridge
public suspend fun sendImage(image: File): MessageReceipt<TSubject> = subject.sendImage(image) public suspend fun sendImage(image: File): MessageReceipt<TSubject>
// endregion // endregion
// region 上传图片 (扩展) // region 上传图片 (扩展)
@JvmSynthetic @JvmSynthetic
public suspend fun BufferedImage.upload(): Image = upload(subject) public suspend fun BufferedImage.upload(): Image
@JvmSynthetic @JvmSynthetic
public suspend fun InputStream.uploadAsImage(): Image = uploadAsImage(subject) public suspend fun InputStream.uploadAsImage(): Image
@JvmSynthetic @JvmSynthetic
public suspend fun File.uploadAsImage(): Image = uploadAsImage(subject) public suspend fun File.uploadAsImage(): Image
// endregion 上传图片 (扩展) // endregion 上传图片 (扩展)
// region 发送图片 (扩展) // region 发送图片 (扩展)
@JvmSynthetic @JvmSynthetic
public suspend fun BufferedImage.send(): MessageReceipt<TSubject> = sendTo(subject) public suspend fun BufferedImage.send(): MessageReceipt<TSubject>
@JvmSynthetic @JvmSynthetic
public suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) public suspend fun InputStream.sendAsImage(): MessageReceipt<TSubject>
@JvmSynthetic @JvmSynthetic
public suspend fun File.sendAsImage(): MessageReceipt<TSubject> = sendAsImageTo(subject) public suspend fun File.sendAsImage(): MessageReceipt<TSubject>
// endregion 发送图片 (扩展) // endregion 发送图片 (扩展)
} }

View File

@ -18,7 +18,7 @@ import net.mamoe.mirai.message.data.*
* 使用 `mirai-serialization` `String.parseMiraiCode()` 转回 [Message]. * 使用 `mirai-serialization` `String.parseMiraiCode()` 转回 [Message].
* *
* ## 规范 * ## 规范
* 可在 [mirai-code-specification.md](https://github.com/mamoe/mirai/blob/dev/docs/mirai-code-specification.md) 查看 mirai 码规范. * 可在 [MiraiCodeSepecification.md](https://github.com/mamoe/mirai/blob/dev/docs/MiraiCodeSepecification.md) 查看 mirai 码规范.
* *
* @suppress 警告: API 可能在任何时刻被改变 * @suppress 警告: API 可能在任何时刻被改变
* *

View File

@ -16,8 +16,10 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.LowLevelApi
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.UserOrBot import net.mamoe.mirai.contact.UserOrBot
import net.mamoe.mirai.contact.nameCardOrNick
import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.utils.PlannedRemoval import net.mamoe.mirai.utils.PlannedRemoval
@ -37,6 +39,22 @@ public data class At(
public override fun toString(): String = "[mirai:at:$target]" public override fun toString(): String = "[mirai:at:$target]"
public override fun contentToString(): String = "@$target" public override fun contentToString(): String = "@$target"
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated("Use getDisplay", ReplaceWith("this.getDisplay()"), DeprecationLevel.ERROR)
@PlannedRemoval("2.0-M2")
val display: Nothing
get() = error("At.display is no longer supported")
/**
* 获取 [At] 发送于指定 [Group] 时会显示的内容.
*
* [group] `null` 且包含成员 [target], 返回 `"@成员名片或昵称"`. 否则返回 `"@123456"` 其中 123456 表示 [target]
*/
public fun getDisplay(group: Group?): String {
val member = group?.get(this.target) ?: return "@$target"
return "@${member.nameCardOrNick}"
}
public companion object { public companion object {
/** /**
* 构造一个 [At], 仅供内部使用, 否则可能造成消息无法发出的问题. * 构造一个 [At], 仅供内部使用, 否则可能造成消息无法发出的问题.

View File

@ -17,6 +17,7 @@ import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.code.CodableMessage
import net.mamoe.mirai.message.data.VipFace.Kind import net.mamoe.mirai.message.data.VipFace.Kind
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.castOrNull
import net.mamoe.mirai.utils.safeCast import net.mamoe.mirai.utils.safeCast
@ -56,7 +57,14 @@ public data class PokeMessage internal constructor(
public val id: Int public val id: Int
) : HummerMessage(), CodableMessage { ) : HummerMessage(), CodableMessage {
@ExperimentalMessageKey @ExperimentalMessageKey
override val key: MessageKey<HummerMessage> get() = Key override val key: MessageKey<HummerMessage>
get() = Key
@PlannedRemoval("2.0-M2")
@Deprecated("Use pokeType", ReplaceWith("pokeType"), DeprecationLevel.ERROR)
val type: Int
get() = pokeType
public companion object Key : public companion object Key :
AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) { AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) {
@ -206,7 +214,8 @@ public data class VipFace internal constructor(
} }
@ExperimentalMessageKey @ExperimentalMessageKey
override val key: MessageKey<VipFace> get() = Key override val key: MessageKey<VipFace>
get() = Key
@Suppress("DEPRECATION_ERROR", "DEPRECATION", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @Suppress("DEPRECATION_ERROR", "DEPRECATION", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
public companion object Key : public companion object Key :

View File

@ -28,6 +28,7 @@ import net.mamoe.mirai.message.data.MessageSource.Key.isAboutGroup
import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp import net.mamoe.mirai.message.data.MessageSource.Key.isAboutTemp
import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.MessageSource.Key.quote
import net.mamoe.mirai.utils.LazyProperty import net.mamoe.mirai.utils.LazyProperty
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.safeCast import net.mamoe.mirai.utils.safeCast
/** /**
@ -145,7 +146,8 @@ public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle {
/** /**
* 返回 `"[mirai:source:${ids.contentToString()},${internalIds.contentToString()}]"` * 返回 `"[mirai:source:${ids.contentToString()},${internalIds.contentToString()}]"`
*/ */
public final override fun toString(): String = "[mirai:source:${ids.contentToString()},${internalIds.contentToString()}]" public final override fun toString(): String =
"[mirai:source:${ids.contentToString()},${internalIds.contentToString()}]"
public companion object Key : AbstractMessageKey<MessageSource>({ it.safeCast() }) { public companion object Key : AbstractMessageKey<MessageSource>({ it.safeCast() }) {
/** /**
@ -397,6 +399,13 @@ public abstract class OfflineMessageSource : MessageSource() {
* 消息种类 * 消息种类
*/ */
public abstract val kind: MessageSourceKind public abstract val kind: MessageSourceKind
@PlannedRemoval("2.0-M2")
@Deprecated(
"Use MessageSourceKind",
ReplaceWith("MessageSourceKind", "net.mamoe.mirai.message.data.MessageSourceKind")
)
private enum class Kind
} }
@Serializable @Serializable

View File

@ -15,6 +15,7 @@ package net.mamoe.mirai.message.data
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.PlannedRemoval
import net.mamoe.mirai.utils.safeCast import net.mamoe.mirai.utils.safeCast
import kotlin.annotation.AnnotationTarget.* import kotlin.annotation.AnnotationTarget.*
@ -150,6 +151,15 @@ public interface ServiceMessage : RichMessage {
public val serviceId: Int public val serviceId: Int
} }
@Suppress("FunctionName")
@Deprecated(
"Use SimpleServiceMessage.",
ReplaceWith("SimpleServiceMessage(serviceId, content)", "net.mamoe.mirai.message.data.SimpleServiceMessage")
)
@PlannedRemoval("2.0-M2")
public fun ServiceMessage(serviceId: Int, content: String): SimpleServiceMessage =
SimpleServiceMessage(serviceId, content)
@MiraiExperimentalApi @MiraiExperimentalApi
@Serializable @Serializable
public abstract class AbstractServiceMessage : ServiceMessage { public abstract class AbstractServiceMessage : ServiceMessage {

View File

@ -17,7 +17,7 @@ import kotlin.annotation.AnnotationTarget.*
* 这些 API 可能会在任意时刻更改, 且不会发布任何预警. * 这些 API 可能会在任意时刻更改, 且不会发布任何预警.
* 非常不建议在发行版本中使用这些 API. * 非常不建议在发行版本中使用这些 API.
*/ */
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR) @RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@Target( @Target(
CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR,
@ -34,7 +34,7 @@ public annotation class MiraiInternalApi(
* 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 这些 API 不具有稳定性, 且可能会在任意时刻更改.
* 不建议在发行版本中使用这些 API. * 不建议在发行版本中使用这些 API.
*/ */
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING) @RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR)
@MustBeDocumented @MustBeDocumented
@ -48,4 +48,24 @@ public annotation class MiraiExperimentalApi(
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) @Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.SOURCE) @Retention(AnnotationRetention.SOURCE)
@MustBeDocumented @MustBeDocumented
internal annotation class PlannedRemoval(val version: String) internal annotation class PlannedRemoval(val version: String)
/**
* 标记已过时的 Member API
*/
@Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS)
@Retention(AnnotationRetention.BINARY)
@RequiresOptIn(level = RequiresOptIn.Level.ERROR)
@MustBeDocumented
@PlannedRemoval("2.0-M2")
internal annotation class MemberDeprecatedApi(val message: String)
/**
* 该注解仅用于测试 EventHandler
*
* 标注了此注解的意为像处理 java 方法那样处理 kotlin 方法
*/
@Retention(AnnotationRetention.RUNTIME)
internal annotation class EventListenerLikeJava

View File

@ -208,24 +208,22 @@ public open class BotConfiguration { // open for Java
} }
@Suppress("ACTUAL_WITHOUT_EXPECT") @Suppress("ACTUAL_WITHOUT_EXPECT")
public enum class MiraiProtocol constructor( public enum class MiraiProtocol {
/** 协议模块使用的 ID */
@JvmField internal val id: Long
) {
/** /**
* Android 手机. * Android 手机.
*/ */
ANDROID_PHONE(537066439), ANDROID_PHONE,
/** /**
* Android 平板. * Android 平板.
*/ */
ANDROID_PAD(537062409), ANDROID_PAD,
/** /**
* Android 手表. * Android 手表.
* */ * */
ANDROID_WATCH(537061176) ANDROID_WATCH,
} }
public companion object { public companion object {

View File

@ -1,61 +0,0 @@
/*
* Copyright 2019-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 org.junit.jupiter.api.Test;
import java.util.concurrent.atomic.AtomicInteger;
import static kotlin.test.AssertionsKt.assertEquals;
public class JvmMethodEventsTestJava extends SimpleListenerHost {
private final AtomicInteger called = new AtomicInteger(0);
@EventHandler
public void ev(TestEvent event) {
called.incrementAndGet();
}
@EventHandler
public Void ev2(TestEvent event) {
called.incrementAndGet();
return null;
}
@EventHandler
public ListeningStatus ev3(TestEvent event) {
called.incrementAndGet();
return ListeningStatus.LISTENING;
}
@EventHandler
public void ev(TestEvent event, TestEvent event2) {
called.incrementAndGet();
}
@EventHandler
public Void ev2(TestEvent event, TestEvent event2) {
called.incrementAndGet();
return null;
}
@EventHandler
public ListeningStatus ev3(TestEvent event, TestEvent event2) {
called.incrementAndGet();
return ListeningStatus.LISTENING;
}
@Test
public void test() {
Events.registerEvents(this);
EventKt.broadcast(new TestEvent());
assertEquals(6, called.get(), null);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright 2019-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 net.mamoe.mirai.JavaFriendlyAPI
import net.mamoe.mirai.utils.EventListenerLikeJava
import org.junit.jupiter.api.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
@EventListenerLikeJava
@JavaFriendlyAPI
internal class JvmMethodEventsTestJava : SimpleListenerHost() {
private val called = AtomicInteger(0)
@EventHandler
fun ev(event: TestEvent?) {
called.incrementAndGet()
}
@EventHandler
fun ev2(event: TestEvent?): Void? {
called.incrementAndGet()
return null
}
@EventHandler
fun ev3(event: TestEvent?): ListeningStatus? {
called.incrementAndGet()
return ListeningStatus.LISTENING
}
@Test
fun test() {
this.registerEvents()
TestEvent().__broadcastJava()
assertEquals(3, called.get(), null)
}
}

View File

@ -46,6 +46,7 @@ import net.mamoe.mirai.message.data.Image.Key.FRIEND_IMAGE_ID_REGEX_2
import net.mamoe.mirai.message.data.Image.Key.GROUP_IMAGE_ID_REGEX import net.mamoe.mirai.message.data.Image.Key.GROUP_IMAGE_ID_REGEX
import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration
import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.currentTimeSeconds
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
@ -733,7 +734,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
override val nick: String get() = fromNick override val nick: String get() = fromNick
override val remark: String override val remark: String
get() = "" get() = ""
})) }).cast())
} }
} }

View File

@ -77,23 +77,23 @@ internal class GroupImpl(
val uin: Long = groupInfo.uin val uin: Long = groupInfo.uin
override lateinit var owner: Member override lateinit var owner: NormalMember
override lateinit var botAsMember: Member override lateinit var botAsMember: NormalMember
override val botPermission: MemberPermission get() = botAsMember.permission override val botPermission: MemberPermission get() = botAsMember.permission
// e.g. 600 // e.g. 600
override val botMuteRemaining: Int get() = botAsMember.muteTimeRemaining override val botMuteRemaining: Int get() = botAsMember.muteTimeRemaining
override val members: ContactList<Member> = ContactList(members.mapNotNull { override val members: ContactList<NormalMember> = ContactList(members.mapNotNull {
if (it.uin == bot.id) { if (it.uin == bot.id) {
botAsMember = newMember(it) botAsMember = newMember(it).cast()
if (it.permission == MemberPermission.OWNER) { if (it.permission == MemberPermission.OWNER) {
owner = botAsMember owner = botAsMember
} }
null null
} else newMember(it).also { member -> } else newMember(it).cast<NormalMember>().also { member ->
if (member.permission == MemberPermission.OWNER) { if (member.permission == MemberPermission.OWNER) {
owner = member owner = member
} }
@ -250,7 +250,7 @@ internal class GroupImpl(
} }
) )
override operator fun get(id: Long): Member? { override operator fun get(id: Long): NormalMember? {
if (id == bot.id) { if (id == bot.id) {
return botAsMember return botAsMember
} }
@ -298,7 +298,7 @@ internal class GroupImpl(
throw EventCancelledException("exception thrown when broadcasting GroupMessagePreSendEvent", it) throw EventCancelledException("exception thrown when broadcasting GroupMessagePreSendEvent", it)
}.message.asMessageChain() }.message.asMessageChain()
val length = chain.estimateLength(703) // 阈值为700左右限制到3的倍数 val length = chain.estimateLength(this, 703) // 阈值为700左右限制到3的倍数
var imageCnt = 0 // 通过下方逻辑短路延迟计算 var imageCnt = 0 // 通过下方逻辑短路延迟计算
if (length > 5000 || chain.count { it is Image }.apply { imageCnt = this } > 50) { if (length > 5000 || chain.count { it is Image }.apply { imageCnt = this } > 50) {

View File

@ -44,7 +44,7 @@ internal class MemberImpl constructor(
group: GroupImpl, group: GroupImpl,
coroutineContext: CoroutineContext, coroutineContext: CoroutineContext,
memberInfo: MemberInfo memberInfo: MemberInfo
) : Member { ) : NormalMember {
override val group: GroupImpl by group.unsafeWeakRef() override val group: GroupImpl by group.unsafeWeakRef()
override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob(coroutineContext[Job]) override val coroutineContext: CoroutineContext = coroutineContext + SupervisorJob(coroutineContext[Job])

View File

@ -45,6 +45,7 @@ import net.mamoe.mirai.network.RetryLaterException
import net.mamoe.mirai.network.UnsupportedSMSLoginException import net.mamoe.mirai.network.UnsupportedSMSLoginException
import net.mamoe.mirai.network.WrongPasswordException import net.mamoe.mirai.network.WrongPasswordException
import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.*
import java.util.concurrent.ConcurrentLinkedQueue
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
@ -220,8 +221,8 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
@JvmField @JvmField
@Volatile @Volatile
internal var pendingIncomingPackets: LockFreeLinkedList<KnownPacketFactories.IncomingPacket<*>>? = internal var pendingIncomingPackets: ConcurrentLinkedQueue<KnownPacketFactories.IncomingPacket<*>>? =
LockFreeLinkedList() ConcurrentLinkedQueue()
private var initFriendOk = false private var initFriendOk = false
private var initGroupOk = false private var initGroupOk = false
@ -322,7 +323,7 @@ internal class QQAndroidBotNetworkHandler(coroutineContext: CoroutineContext, bo
} }
if (!pendingEnabled) { if (!pendingEnabled) {
pendingIncomingPackets = LockFreeLinkedList() pendingIncomingPackets = ConcurrentLinkedQueue()
_pendingEnabled.value = true _pendingEnabled.value = true
} }

View File

@ -76,9 +76,10 @@ internal open class QQAndroidClient(
val device: DeviceInfo, val device: DeviceInfo,
bot: QQAndroidBot bot: QQAndroidBot
) { ) {
@Suppress("INVISIBLE_MEMBER") val protocol = MiraiProtocolInternal[bot.configuration.protocol]
val subAppId: Long val subAppId: Long
get() = bot.configuration.protocol.id get() = protocol.id
internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList() internal val serverList: MutableList<Pair<String, Int>> = DefaultServerList.toMutableList()
@ -118,9 +119,10 @@ internal open class QQAndroidClient(
var tgtgtKey: ByteArray = generateTgtgtKey(device.guid) var tgtgtKey: ByteArray = generateTgtgtKey(device.guid)
val randomKey: ByteArray = getRandomByteArray(16) val randomKey: ByteArray = getRandomByteArray(16)
var miscBitMap: Int = 184024956 // 也可能是 150470524 ?
private var mainSigMap: Int = 16724722 val miscBitMap: Int get() = protocol.miscBitMap // 184024956 // 也可能是 150470524 ?
var subSigMap: Int = 0x10400 //=66,560 private val mainSigMap: Int = protocol.mainSigMap
var subSigMap: Int = protocol.subSigMap // 0x10400 //=66,560
private val _ssoSequenceId: AtomicInt = atomic(85600) private val _ssoSequenceId: AtomicInt = atomic(85600)
@ -157,9 +159,12 @@ internal open class QQAndroidClient(
var openAppId: Long = 715019303L var openAppId: Long = 715019303L
val apkVersionName: ByteArray get() = "8.4.18".toByteArray() val apkVersionName: ByteArray get() = protocol.ver.toByteArray() //"8.4.18".toByteArray()
val buildVer: String get() = "8.4.18.4810" // 8.2.0.1296 // 8.4.8.4810 // 8.2.7.4410 val buildVer: String get() = "8.4.18.4810" // 8.2.0.1296 // 8.4.8.4810 // 8.2.7.4410
val buildTime: Long get() = protocol.buildTime
val sdkVersion: String get() = protocol.sdkVer
private val messageSequenceId: AtomicInt = atomic(22911) private val messageSequenceId: AtomicInt = atomic(22911)
internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2) internal fun atomicNextMessageSequenceId(): Int = messageSequenceId.getAndAdd(2)
@ -194,7 +199,7 @@ internal open class QQAndroidClient(
var networkType: NetworkType = NetworkType.WIFI var networkType: NetworkType = NetworkType.WIFI
val apkSignatureMd5: ByteArray = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes() val apkSignatureMd5: ByteArray get() = protocol.sign.hexToBytes() // "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes()
/** /**
* 协议版本?, 8.2.7 的为 8001 * 协议版本?, 8.2.7 的为 8001

View File

@ -184,60 +184,59 @@ internal object KnownPacketFactories {
bot: QQAndroidBot, bot: QQAndroidBot,
rawInput: ByteReadPacket, rawInput: ByteReadPacket,
consumer: PacketConsumer<T> consumer: PacketConsumer<T>
) = ): Unit = with(rawInput) {
with(rawInput) { // login
// login val flag1 = readInt()
val flag1 = readInt()
PacketLogger.verbose { "开始处理一个包" } PacketLogger.verbose { "开始处理一个包" }
val flag2 = readByte().toInt() val flag2 = readByte().toInt()
val flag3 = readByte().toInt() val flag3 = readByte().toInt()
check(flag3 == 0) { check(flag3 == 0) {
"Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " + "Illegal flag3. Expected 0, whereas got $flag3. flag1=$flag1, flag2=$flag2. " +
"Remaining=${this.readBytes().toUHexString()}" "Remaining=${this.readBytes().toUHexString()}"
} }
readString(readInt() - 4)// uinAccount readString(readInt() - 4)// uinAccount
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
ByteArrayPool.useInstance(this.remaining.toInt()) { data -> ByteArrayPool.useInstance(this.remaining.toInt()) { data ->
val size = this.readAvailable(data) val size = this.readAvailable(data)
kotlin.runCatching { kotlin.runCatching {
when (flag2) { when (flag2) {
2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size) 2 -> TEA.decrypt(data, DECRYPTER_16_ZERO, size)
1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size) 1 -> TEA.decrypt(data, bot.client.wLoginSigInfo.d2Key, size)
0 -> data 0 -> data
else -> error("") else -> error("")
}
}.getOrElse {
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<T>
if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
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)
}
} ?: kotlin.run {
PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" }
return
} }
}.getOrElse {
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<T>
if (it.packetFactory is IncomingPacketFactory<T> && it.packetFactory.canBeCached && bot.network.pendingEnabled) {
@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
bot.network.pendingIncomingPackets?.add(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)
}
} ?: kotlin.run {
PacketLogger.error { "任何key都无法解密: ${data.take(size).toUHexString()}" }
return
} }
} }
}
internal suspend fun <T : Packet?> handleIncomingPacket( internal suspend fun <T : Packet?> handleIncomingPacket(
it: IncomingPacket<T>, it: IncomingPacket<T>,

View File

@ -120,7 +120,7 @@ internal fun BytePacketBuilder.t106(
writeByte(isSavePassword.toByte()) writeByte(isSavePassword.toByte())
writeFully(passwordMd5) writeFully(passwordMd5)
writeFully(tgtgtKey) writeFully(tgtgtKey)
writeInt(0) writeInt(0) // wtf
writeByte(isGuidAvailable.toByte()) writeByte(isGuidAvailable.toByte())
if (isGuidAvailable) { if (isGuidAvailable) {
require(guid != null) { "Guid must not be null when isGuidAvailable==true" } require(guid != null) { "Guid must not be null when isGuidAvailable==true" }
@ -193,7 +193,7 @@ internal fun BytePacketBuilder.t107(
internal fun BytePacketBuilder.t108( internal fun BytePacketBuilder.t108(
ksid: ByteArray ksid: ByteArray
) { ) {
require(ksid.size == 16) { "ksid should length 16" } // require(ksid.size == 16) { "ksid should length 16" }
writeShort(0x108) writeShort(0x108)
writeShortLVPacket { writeShortLVPacket {
writeFully(ksid) writeFully(ksid)

View File

@ -22,6 +22,7 @@ import kotlinx.io.core.discardExact
import net.mamoe.mirai.Mirai import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.contact.NormalMember
import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.MemberInfo
import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.AbstractEvent
import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.Event
@ -51,6 +52,7 @@ import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
import net.mamoe.mirai.internal.utils.read import net.mamoe.mirai.internal.utils.read
import net.mamoe.mirai.internal.utils.toInt import net.mamoe.mirai.internal.utils.toInt
import net.mamoe.mirai.internal.utils.toUHexString import net.mamoe.mirai.internal.utils.toUHexString
import net.mamoe.mirai.utils.cast
import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.debug
import net.mamoe.mirai.utils.warning import net.mamoe.mirai.utils.warning
import kotlin.random.Random import kotlin.random.Random
@ -235,10 +237,12 @@ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Re
readByte().toInt().and(0xff) readByte().toInt().and(0xff)
} == 0x83) { } == 0x83) {
return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo()) return@mapNotNull MemberJoinEvent.Invite(group.newMember(msg.getNewMemberInfo())
.cast<NormalMember>()
.also { group.members.delegate.add(it) }) .also { group.members.delegate.add(it) })
} }
return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo()) return@mapNotNull MemberJoinEvent.Active(group.newMember(msg.getNewMemberInfo())
.cast<NormalMember>()
.also { group.members.delegate.add(it) }) .also { group.members.delegate.add(it) })
} }
} }

View File

@ -128,7 +128,7 @@ internal class WtLogin {
writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) { writeSsoPacket(client, client.subAppId, commandName, sequenceId = sequenceId) {
writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) { writeOicqRequestPacket(client, EncryptMethodECDH(client.ecdh), 0x0810) {
writeShort(9) // subCommand writeShort(9) // subCommand
writeShort(17) // count of TLVs, probably ignored by server? writeShort(0x18) // count of TLVs, probably ignored by server?
//writeShort(LoginType.PASSWORD.value.toShort()) //writeShort(LoginType.PASSWORD.value.toShort())
t18(appId, client.appClientVersion, client.uin) t18(appId, client.appClientVersion, client.uin)
@ -161,7 +161,8 @@ internal class WtLogin {
*/ */
t116(client.miscBitMap, client.subSigMap) t116(client.miscBitMap, client.subSigMap)
t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion) t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion)
t107(6) t107(0)
t108(client.device.imei.toByteArray())
// t108(byteArrayOf()) // t108(byteArrayOf())
// ignored: t104() // ignored: t104()
@ -192,9 +193,11 @@ internal class WtLogin {
t145(client.device.guid) t145(client.device.guid)
t147(appId, client.apkVersionName, client.apkSignatureMd5) t147(appId, client.apkVersionName, client.apkSignatureMd5)
/*
if (client.miscBitMap and 0x80 != 0) { if (client.miscBitMap and 0x80 != 0) {
t166(1) t166(1)
} }
*/
// ignored t16a because array5 is null // ignored t16a because array5 is null
@ -210,14 +213,14 @@ internal class WtLogin {
"connect.qq.com", "connect.qq.com",
"qzone.qq.com", "qzone.qq.com",
"vip.qq.com", "vip.qq.com",
"gamecenter.qq.com",
"qun.qq.com", "qun.qq.com",
"game.qq.com", "game.qq.com",
"qqweb.qq.com", "qqweb.qq.com",
"office.qq.com", "office.qq.com",
"ti.qq.com", "ti.qq.com",
"mail.qq.com", "mail.qq.com",
"qzone.com", "mma.qq.com",
"mma.qq.com"
) )
) )
@ -243,7 +246,10 @@ internal class WtLogin {
t202(bssid, ssid) t202(bssid, ssid)
} }
t177() t177(
buildTime = client.buildTime,
buildVersion = client.sdkVersion,
)
t516() t516()
t521() t521()

View File

@ -0,0 +1,69 @@
/*
* Copyright 2019-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.internal.utils
import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol
import java.util.*
internal class MiraiProtocolInternal(
@JvmField internal val apkId: String,
@JvmField internal val id: Long,
@JvmField internal val ver: String,
@JvmField internal val sdkVer: String,
@JvmField internal val miscBitMap: Int,
@JvmField internal val subSigMap: Int,
@JvmField internal val mainSigMap: Int,
@JvmField internal val sign: String,
@JvmField internal val buildTime: Long,
) {
internal companion object {
internal val protocols = EnumMap<MiraiProtocol, MiraiProtocolInternal>(
MiraiProtocol::class.java
)
operator fun get(protocol: MiraiProtocol): MiraiProtocolInternal =
protocols[protocol] ?: error("Internal Error: Missing protocol $protocol")
init {
protocols[MiraiProtocol.ANDROID_PHONE] = MiraiProtocolInternal(
"com.tencent.mobileqq",
537066419,
"8.4.18",
"6.0.0.2454",
184024956,
0x10400,
34869472,
"A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
1604580615L,
)
protocols[MiraiProtocol.ANDROID_PAD] = MiraiProtocolInternal(
"com.tencent.mobileqq",
537062409, "8.4.18",
"6.0.0.2454",
184024956,
0x10400,
34869472,
"A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
1604580615L,
)
protocols[MiraiProtocol.ANDROID_WATCH] = MiraiProtocolInternal(
"com.tencent.mobileqq",
537061176,
"8.2.7",
"6.0.0.2413",
184024956,
0x10400,
34869472,
"A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D",
1571193922L
)
}
}
}

View File

@ -12,10 +12,10 @@
package net.mamoe.mirai.internal.utils package net.mamoe.mirai.internal.utils
import net.mamoe.mirai.contact.ContactOrBot
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.data.AtAll.display import net.mamoe.mirai.message.data.AtAll.display
import kotlin.jvm.JvmMultifileClass import net.mamoe.mirai.utils.safeCast
import kotlin.jvm.JvmName
internal fun Int.toIpV4AddressString(): String { internal fun Int.toIpV4AddressString(): String {
@ -43,17 +43,17 @@ internal fun String.chineseLength(upTo: Int): Int {
} }
} }
internal fun MessageChain.estimateLength(upTo: Int): Int = internal fun MessageChain.estimateLength(target: ContactOrBot, upTo: Int): Int =
sumUpTo(upTo) { it, up -> sumUpTo(upTo) { it, up ->
it.estimateLength(up) it.estimateLength(target, up)
} }
internal fun SingleMessage.estimateLength(upTo: Int): Int { internal fun SingleMessage.estimateLength(target: ContactOrBot, upTo: Int): Int {
return when (this) { return when (this) {
is QuoteReply -> 444 + this.source.originalMessage.estimateLength(upTo) // Magic number is QuoteReply -> 444 + this.source.originalMessage.estimateLength(target, upTo) // Magic number
is Image -> 260 // Magic number is Image -> 260 // Magic number
is PlainText -> content.chineseLength(upTo) is PlainText -> content.chineseLength(upTo)
is At -> display.chineseLength(upTo) is At -> this.getDisplay(target.safeCast()).chineseLength(upTo)
is AtAll -> display.chineseLength(upTo) is AtAll -> display.chineseLength(upTo)
else -> this.toString().chineseLength(upTo) else -> this.toString().chineseLength(upTo)
} }